diff --git a/packages/flutter_mozilla_components/.gitignore b/packages/flutter_mozilla_components/.gitignore
new file mode 100644
index 00000000..ea810a52
--- /dev/null
+++ b/packages/flutter_mozilla_components/.gitignore
@@ -0,0 +1,31 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+build/
diff --git a/packages/flutter_mozilla_components/.metadata b/packages/flutter_mozilla_components/.metadata
new file mode 100644
index 00000000..d19de3eb
--- /dev/null
+++ b/packages/flutter_mozilla_components/.metadata
@@ -0,0 +1,30 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "6fe09872b12acb5747d6e01e84987120cabf31df"
+ channel: "master"
+
+project_type: plugin
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 6fe09872b12acb5747d6e01e84987120cabf31df
+ base_revision: 6fe09872b12acb5747d6e01e84987120cabf31df
+ - platform: android
+ create_revision: 6fe09872b12acb5747d6e01e84987120cabf31df
+ base_revision: 6fe09872b12acb5747d6e01e84987120cabf31df
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/packages/flutter_mozilla_components/CHANGELOG.md b/packages/flutter_mozilla_components/CHANGELOG.md
new file mode 100644
index 00000000..41cc7d81
--- /dev/null
+++ b/packages/flutter_mozilla_components/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1
+
+* TODO: Describe initial release.
diff --git a/packages/flutter_mozilla_components/LICENSE b/packages/flutter_mozilla_components/LICENSE
new file mode 100644
index 00000000..ba75c69f
--- /dev/null
+++ b/packages/flutter_mozilla_components/LICENSE
@@ -0,0 +1 @@
+TODO: Add your license here.
diff --git a/packages/flutter_mozilla_components/README.md b/packages/flutter_mozilla_components/README.md
new file mode 100644
index 00000000..82734742
--- /dev/null
+++ b/packages/flutter_mozilla_components/README.md
@@ -0,0 +1,15 @@
+# flutter_mozilla_components
+
+A new Flutter plugin project.
+
+## Getting Started
+
+This project is a starting point for a Flutter
+[plug-in package](https://flutter.dev/to/develop-plugins),
+a specialized package that includes platform-specific implementation code for
+Android and/or iOS.
+
+For help getting started with Flutter development, view the
+[online documentation](https://docs.flutter.dev), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
+
diff --git a/packages/flutter_mozilla_components/analysis_options.yaml b/packages/flutter_mozilla_components/analysis_options.yaml
new file mode 100644
index 00000000..a5744c1c
--- /dev/null
+++ b/packages/flutter_mozilla_components/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/packages/flutter_mozilla_components/android/.gitignore b/packages/flutter_mozilla_components/android/.gitignore
new file mode 100644
index 00000000..161bdcda
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/packages/flutter_mozilla_components/android/build.gradle b/packages/flutter_mozilla_components/android/build.gradle
new file mode 100644
index 00000000..e33929d4
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/build.gradle
@@ -0,0 +1,112 @@
+group = "eu.lensai.flutter_mozilla_components"
+version = "1.0-SNAPSHOT"
+
+buildscript {
+ ext.kotlin_version = "1.9.20"
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath("com.android.tools.build:gradle:8.6.0")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
+ }
+}
+
+rootProject.allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ maven {
+ url "https://maven.mozilla.org/maven2/"
+ }
+ }
+}
+
+apply plugin: "com.android.library"
+apply plugin: "kotlin-android"
+
+android {
+ if (project.android.hasProperty("namespace")) {
+ namespace = "eu.lensai.flutter_mozilla_components"
+ }
+
+ compileSdk = 35
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ jvmTarget = '17'
+ }
+
+ sourceSets {
+ main.java.srcDirs += "src/main/kotlin"
+ test.java.srcDirs += "src/test/kotlin"
+ }
+
+ defaultConfig {
+ minSdk = 21
+ }
+
+ buildFeatures {
+ viewBinding true
+ }
+
+ dependencies {
+ implementation 'org.mozilla.components:support-utils:131.0b1'
+ implementation 'org.mozilla.components:support-ktx:131.0b1'
+ implementation 'org.mozilla.components:support-webextensions:131.0b1'
+ implementation 'org.mozilla.components:support-locale:131.0b1'
+ implementation 'org.mozilla.components:lib-fetch-httpurlconnection:131.0b1'
+ implementation 'org.mozilla.components:lib-crash:131.0b1'
+ implementation 'org.mozilla.components:lib-publicsuffixlist:131.0b1'
+ implementation 'org.mozilla.components:lib-dataprotect:131.0b1'
+ implementation 'org.mozilla.components:concept-engine:131.0b1'
+ implementation 'org.mozilla.components:concept-fetch:131.0b1'
+ implementation 'org.mozilla.components:browser-engine-gecko:131.0b1'
+ implementation 'org.mozilla.components:browser-state:131.0b1'
+ implementation 'org.mozilla.components:browser-session-storage:131.0b1'
+ implementation 'org.mozilla.components:browser-icons:131.0b1'
+ //implementation 'org.mozilla.components:browser-storage-sync:131.0b1'
+ implementation 'org.mozilla.components:browser-thumbnails:131.0b1'
+ implementation 'org.mozilla.components:feature-addons:131.0b1'
+ implementation 'org.mozilla.components:feature-app-links:131.0b1'
+ implementation 'org.mozilla.components:feature-autofill:131.0b1'
+ implementation 'org.mozilla.components:feature-downloads:131.0b1'
+ implementation 'org.mozilla.components:feature-media:131.0b1'
+ implementation 'org.mozilla.components:feature-tabs:131.0b1'
+ implementation 'org.mozilla.components:feature-prompts:131.0b1'
+ implementation 'org.mozilla.components:feature-session:131.0b1'
+ implementation 'org.mozilla.components:feature-readerview:131.0b1'
+ implementation 'org.mozilla.components:feature-privatemode:131.0b1'
+ implementation 'org.mozilla.components:feature-sitepermissions:131.0b1'
+ implementation 'org.mozilla.components:feature-webcompat:131.0b1'
+ implementation 'org.mozilla.components:feature-webnotifications:131.0b1'
+ implementation 'org.mozilla.components:service-digitalassetlinks:131.0b1'
+ implementation 'org.mozilla.components:ui-widgets:131.0b1'
+
+ implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
+ implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
+ implementation 'com.google.android.material:material:1.12.0'
+ //https://stackoverflow.com/questions/73782320/onbackinvokedcallback-is-not-enabled-for-the-application-in-set-androidenableo
+ implementation 'androidx.activity:activity:1.9.1'
+
+ testImplementation("org.jetbrains.kotlin:kotlin-test")
+ testImplementation("org.mockito:mockito-core:5.0.0")
+ }
+
+ testOptions {
+ unitTests.all {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed", "standardOut", "standardError"
+ outputs.upToDateWhen {false}
+ showStandardStreams = true
+ }
+ }
+ }
+}
diff --git a/packages/flutter_mozilla_components/android/settings.gradle b/packages/flutter_mozilla_components/android/settings.gradle
new file mode 100644
index 00000000..4e797632
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'flutter_mozilla_components'
diff --git a/packages/flutter_mozilla_components/android/src/main/AndroidManifest.xml b/packages/flutter_mozilla_components/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..28362022
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/AppRequestInterceptor.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/AppRequestInterceptor.kt
new file mode 100644
index 00000000..5f2c84d1
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/AppRequestInterceptor.kt
@@ -0,0 +1,64 @@
+package eu.lensai.flutter_mozilla_components
+
+
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import mozilla.components.browser.errorpages.ErrorPages
+import mozilla.components.browser.errorpages.ErrorType
+import mozilla.components.concept.engine.EngineSession
+import mozilla.components.concept.engine.request.RequestInterceptor
+
+class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
+ override fun onLoadRequest(
+ engineSession: EngineSession,
+ uri: String,
+ lastUri: String?,
+ hasUserGesture: Boolean,
+ isSameDomain: Boolean,
+ isRedirect: Boolean,
+ isDirectNavigation: Boolean,
+ isSubframeRequest: Boolean,
+ ): RequestInterceptor.InterceptionResponse? {
+ val components = GlobalComponents.components!!
+
+ return when (uri) {
+// "about:privatebrowsing" -> {
+// val page = PrivatePage.createPrivateBrowsingPage(context, uri)
+// RequestInterceptor.InterceptionResponse.Content(page, encoding = "base64")
+// }
+
+// "about:crashes" -> {
+// val intent = Intent(context, CrashListActivity::class.java)
+// intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
+// context.startActivity(intent)
+//
+// RequestInterceptor.InterceptionResponse.Url("about:blank")
+// }
+
+ else -> {
+ components.appLinksInterceptor.onLoadRequest(
+ engineSession,
+ uri,
+ lastUri,
+ hasUserGesture,
+ isSameDomain,
+ isRedirect,
+ isDirectNavigation,
+ isSubframeRequest,
+ )
+ }
+ }
+ }
+
+ override fun onErrorRequest(
+ session: EngineSession,
+ errorType: ErrorType,
+ uri: String?,
+ ): RequestInterceptor.ErrorResponse {
+ val errorPage = ErrorPages.createUrlEncodedErrorPage(context, errorType, uri)
+ return RequestInterceptor.ErrorResponse(errorPage)
+ }
+
+ override fun interceptsAppInitiatedRequests() = true
+}
\ No newline at end of file
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BaseBrowserFragment.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BaseBrowserFragment.kt
new file mode 100644
index 00000000..89561ce0
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BaseBrowserFragment.kt
@@ -0,0 +1,273 @@
+package eu.lensai.flutter_mozilla_components
+
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.annotation.CallSuper
+import androidx.fragment.app.Fragment
+import eu.lensai.flutter_mozilla_components.databinding.FragmentBrowserBinding
+import mozilla.components.concept.engine.EngineView
+import mozilla.components.feature.app.links.AppLinksFeature
+import mozilla.components.feature.downloads.DownloadsFeature
+import mozilla.components.feature.downloads.manager.FetchDownloadManager
+import mozilla.components.feature.privatemode.feature.SecureWindowFeature
+import mozilla.components.feature.prompts.PromptFeature
+import mozilla.components.feature.session.SessionFeature
+import mozilla.components.feature.session.SwipeRefreshFeature
+import mozilla.components.feature.sitepermissions.SitePermissionsFeature
+import mozilla.components.feature.sitepermissions.SitePermissionsRules
+import mozilla.components.feature.sitepermissions.SitePermissionsRules.AutoplayAction
+import mozilla.components.support.base.feature.ActivityResultHandler
+import mozilla.components.support.base.feature.UserInteractionHandler
+import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
+import mozilla.components.support.base.log.logger.Logger
+import mozilla.components.support.ktx.android.arch.lifecycle.addObservers
+import mozilla.components.support.locale.ActivityContextWrapper
+import mozilla.components.support.utils.ext.requestInPlacePermissions
+
+/**
+ * Base fragment extended by [BrowserFragment] and [ExternalAppBrowserFragment].
+ * This class only contains shared code focused on the main browsing content.
+ * UI code specific to the app or to custom tabs can be found in the subclasses.
+ */
+@SuppressWarnings("LargeClass")
+abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, ActivityResultHandler {
+ private val sessionFeature = ViewBoundFeatureWrapper()
+ private val downloadsFeature = ViewBoundFeatureWrapper()
+ private val appLinksFeature = ViewBoundFeatureWrapper()
+ private val promptFeature = ViewBoundFeatureWrapper()
+ private val sitePermissionsFeature = ViewBoundFeatureWrapper()
+ private val swipeRefreshFeature = ViewBoundFeatureWrapper()
+
+ protected val sessionId: String?
+ get() = arguments?.getString(SESSION_ID_KEY)
+
+ private val activityResultHandler: List> = listOf(
+ promptFeature,
+ )
+
+ private var _engineView: EngineView? = null
+ private var _binding: FragmentBrowserBinding? = null
+ private var _components: Components? = null
+
+ val binding get() = _binding!!
+ val components get() = _components!!
+ val engineView get() = _engineView!!
+
+ protected abstract fun createEngine(components: Components) : EngineView
+
+ @CallSuper
+ @Suppress("LongMethod")
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ _binding = FragmentBrowserBinding.inflate(inflater, container, false)
+ _components = GlobalComponents.components
+
+ _engineView = createEngine(components)
+
+ val engineNativeView = engineView.asView()
+ // Set layout parameters
+ val layoutParams = FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT
+ )
+ engineNativeView.layoutParams = layoutParams
+
+ binding.swipeToRefresh.addView(engineNativeView)
+
+ val originalContext = ActivityContextWrapper.getOriginalContext(requireActivity())
+ engineView.setActivityContext(originalContext)
+
+ sessionFeature.set(
+ feature = SessionFeature(
+ components.store,
+ components.sessionUseCases.goBack,
+ components.sessionUseCases.goForward,
+ engineView,
+ sessionId,
+ ),
+ owner = this,
+ view = binding.root,
+ )
+
+ swipeRefreshFeature.set(
+ feature = SwipeRefreshFeature(
+ components.store,
+ components.sessionUseCases.reload,
+ binding.swipeToRefresh,
+ ),
+ owner = this,
+ view = binding.root,
+ )
+
+ downloadsFeature.set(
+ feature = DownloadsFeature(
+ requireContext().applicationContext,
+ store = components.store,
+ useCases = components.downloadsUseCases,
+ fragmentManager = childFragmentManager,
+ onDownloadStopped = { download, id, status ->
+ Logger.debug("Download done. ID#$id $download with status $status")
+ },
+ downloadManager = FetchDownloadManager(
+ requireContext().applicationContext,
+ components.store,
+ DownloadService::class,
+ notificationsDelegate = components.notificationsDelegate,
+ ),
+ tabId = sessionId,
+ onNeedToRequestPermissions = { permissions ->
+ requestInPlacePermissions(REQUEST_KEY_DOWNLOAD_PERMISSIONS, permissions) { result ->
+ downloadsFeature.get()?.onPermissionsResult(
+ result.keys.toTypedArray(),
+ result.values.map {
+ when (it) {
+ true -> PackageManager.PERMISSION_GRANTED
+ false -> PackageManager.PERMISSION_DENIED
+ }
+ }.toIntArray(),
+ )
+ }
+ },
+ ),
+ owner = this,
+ view = binding.root,
+ )
+
+ appLinksFeature.set(
+ feature = AppLinksFeature(
+ context = requireContext(),
+ store = components.store,
+ sessionId = sessionId,
+ fragmentManager = parentFragmentManager,
+ launchInApp = { components.preferences.getBoolean(DefaultComponents.PREF_LAUNCH_EXTERNAL_APP, false) },
+ loadUrlUseCase = components.sessionUseCases.loadUrl,
+ ),
+ owner = this,
+ view = binding.root,
+ )
+
+ promptFeature.set(
+ feature = PromptFeature(
+ fragment = this,
+ store = components.store,
+ customTabId = sessionId,
+ tabsUseCases = components.tabsUseCases,
+ fragmentManager = parentFragmentManager,
+ fileUploadsDirCleaner = components.fileUploadsDirCleaner,
+ onNeedToRequestPermissions = { permissions ->
+ requestInPlacePermissions(REQUEST_KEY_PROMPT_PERMISSIONS, permissions) { result ->
+ promptFeature.get()?.onPermissionsResult(
+ result.keys.toTypedArray(),
+ result.values.map {
+ when (it) {
+ true -> PackageManager.PERMISSION_GRANTED
+ false -> PackageManager.PERMISSION_DENIED
+ }
+ }.toIntArray(),
+ )
+ }
+ },
+ ),
+ owner = this,
+ view = binding.root,
+ )
+
+ sitePermissionsFeature.set(
+ feature = SitePermissionsFeature(
+ context = requireContext(),
+ sessionId = sessionId,
+ storage = components.permissionStorage,
+ fragmentManager = parentFragmentManager,
+ sitePermissionsRules = SitePermissionsRules(
+ autoplayAudible = AutoplayAction.BLOCKED,
+ autoplayInaudible = AutoplayAction.BLOCKED,
+ camera = SitePermissionsRules.Action.ASK_TO_ALLOW,
+ location = SitePermissionsRules.Action.ASK_TO_ALLOW,
+ notification = SitePermissionsRules.Action.ASK_TO_ALLOW,
+ microphone = SitePermissionsRules.Action.ASK_TO_ALLOW,
+ persistentStorage = SitePermissionsRules.Action.ASK_TO_ALLOW,
+ mediaKeySystemAccess = SitePermissionsRules.Action.ASK_TO_ALLOW,
+ crossOriginStorageAccess = SitePermissionsRules.Action.ASK_TO_ALLOW,
+ ),
+ onNeedToRequestPermissions = { permissions ->
+ requestInPlacePermissions(REQUEST_KEY_SITE_PERMISSIONS, permissions) { result ->
+ sitePermissionsFeature.get()?.onPermissionsResult(
+ result.keys.toTypedArray(),
+ result.values.map {
+ when (it) {
+ true -> PackageManager.PERMISSION_GRANTED
+ false -> PackageManager.PERMISSION_DENIED
+ }
+ }.toIntArray(),
+ )
+ }
+ },
+ onShouldShowRequestPermissionRationale = { shouldShowRequestPermissionRationale(it) },
+ store = components.store,
+ ),
+ owner = this,
+ view = binding.root,
+ )
+
+ val secureWindowFeature = SecureWindowFeature(
+ window = requireActivity().window,
+ store = components.store,
+ customTabId = sessionId,
+ )
+
+ // Observe the lifecycle for supported features
+ lifecycle.addObservers(
+ secureWindowFeature,
+ )
+
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+// consumeFlow(components.store) { flow ->
+// flow.mapNotNull { state -> state.findCustomTabOrSelectedTab(sessionId) }
+// .ifAnyChanged { tab ->
+// arrayOf(
+// tab.content.loading,
+// tab.content.canGoBack,
+// tab.content.canGoForward,
+// )
+// }
+// .collect {
+// binding.toolbar.invalidateActions()
+// }
+// }
+ }
+
+ @CallSuper
+ override fun onBackPressed(): Boolean =
+ listOf(sessionFeature).any { it.onBackPressed() }
+
+ @CallSuper
+ override fun onActivityResult(requestCode: Int, data: Intent?, resultCode: Int): Boolean {
+ return activityResultHandler.any { it.onActivityResult(requestCode, data, resultCode) }
+ }
+
+ companion object {
+ private const val SESSION_ID_KEY = "session_id"
+
+ private const val REQUEST_KEY_DOWNLOAD_PERMISSIONS = "downloadFeature"
+ private const val REQUEST_KEY_PROMPT_PERMISSIONS = "promptFeature"
+ private const val REQUEST_KEY_SITE_PERMISSIONS = "sitePermissionsFeature"
+
+ @JvmStatic
+ protected fun Bundle.putSessionId(sessionId: String?) {
+ putString(SESSION_ID_KEY, sessionId)
+ }
+ }
+ override fun onDestroyView() {
+ super.onDestroyView()
+ engineView.setActivityContext(null)
+ _binding = null
+ }
+}
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BrowserFragment.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BrowserFragment.kt
new file mode 100644
index 00000000..da6029e8
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/BrowserFragment.kt
@@ -0,0 +1,102 @@
+package eu.lensai.flutter_mozilla_components
+
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import mozilla.components.browser.thumbnails.BrowserThumbnails
+import mozilla.components.concept.engine.EngineView
+import mozilla.components.feature.media.fullscreen.MediaSessionFullscreenFeature
+import mozilla.components.feature.session.FullScreenFeature
+import mozilla.components.feature.tabs.WindowFeature
+import mozilla.components.support.base.feature.UserInteractionHandler
+import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
+import mozilla.components.support.ktx.android.view.enterImmersiveMode
+import mozilla.components.support.ktx.android.view.exitImmersiveMode
+
+/**
+ * Fragment used for browsing the web within the main app.
+ */
+class BrowserFragment(private val context: Context) : BaseBrowserFragment(), UserInteractionHandler {
+ private val thumbnailsFeature = ViewBoundFeatureWrapper()
+ private val fullScreenFeature = ViewBoundFeatureWrapper()
+ private val mediaSessionFullscreenFeature =
+ ViewBoundFeatureWrapper()
+
+ override fun createEngine(components: Components): EngineView {
+ return components.engine.createView(context).apply {
+// selectionActionDelegate = DefaultSelectionActionDelegate(
+// store = components.store,
+// context = context,
+// )
+ }
+ }
+
+ @Suppress("LongMethod")
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ super.onCreateView(inflater, container, savedInstanceState)
+
+ val binding = super.binding
+
+ fullScreenFeature.set(
+ feature = FullScreenFeature(
+ components.store,
+ components.sessionUseCases,
+ sessionId,
+ ) { inFullScreen ->
+ if (inFullScreen) {
+ activity?.enterImmersiveMode()
+ } else {
+ activity?.exitImmersiveMode()
+ }
+ },
+ owner = this,
+ view = binding.root,
+ )
+
+ mediaSessionFullscreenFeature.set(
+ feature = MediaSessionFullscreenFeature(
+ requireActivity(),
+ components.store,
+ sessionId,
+ ),
+ owner = this,
+ view = binding.root,
+ )
+
+ thumbnailsFeature.set(
+ feature = BrowserThumbnails(requireContext(), engineView, components.store),
+ owner = this,
+ view = binding.root,
+ )
+
+ val windowFeature = WindowFeature(components.store, components.tabsUseCases)
+ lifecycle.addObserver(windowFeature)
+
+ return binding.root
+ }
+
+
+ override fun onBackPressed(): Boolean {
+ return when {
+ fullScreenFeature.onBackPressed() -> true
+ else -> super.onBackPressed()
+ }
+ }
+
+ companion object {
+ fun create(context: Context, sessionId: String? = null) = BrowserFragment(context).apply {
+ arguments = Bundle().apply {
+ putSessionId(sessionId)
+ }
+ }
+ }
+}
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DefaultComponents.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DefaultComponents.kt
new file mode 100644
index 00000000..76d76f5c
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DefaultComponents.kt
@@ -0,0 +1,255 @@
+package eu.lensai.flutter_mozilla_components
+
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.util.Log
+import android.widget.Toast
+import androidx.core.app.NotificationManagerCompat
+import androidx.core.content.ContextCompat
+import eu.lensai.flutter_mozilla_components.middleware.FlutterEventMiddleware
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.launch
+import mozilla.components.browser.engine.gecko.GeckoEngine
+import mozilla.components.browser.icons.BrowserIcons
+import mozilla.components.browser.session.storage.SessionStorage
+import mozilla.components.browser.state.engine.EngineMiddleware
+import mozilla.components.browser.state.engine.middleware.SessionPrioritizationMiddleware
+import mozilla.components.browser.state.selector.selectedTab
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.browser.thumbnails.ThumbnailsMiddleware
+import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
+import mozilla.components.concept.base.crash.Breadcrumb
+import mozilla.components.concept.engine.DefaultSettings
+import mozilla.components.concept.engine.Engine
+import mozilla.components.concept.engine.mediaquery.PreferredColorScheme
+import mozilla.components.concept.fetch.Client
+import mozilla.components.feature.addons.AddonManager
+import mozilla.components.feature.addons.amo.AMOAddonsProvider
+import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker
+import mozilla.components.feature.addons.update.DefaultAddonUpdater
+import mozilla.components.feature.app.links.AppLinksInterceptor
+import mozilla.components.feature.app.links.AppLinksUseCases
+import mozilla.components.feature.downloads.DownloadMiddleware
+import mozilla.components.feature.downloads.DownloadsUseCases
+import mozilla.components.feature.media.MediaSessionFeature
+import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
+import mozilla.components.feature.prompts.PromptMiddleware
+import mozilla.components.feature.prompts.file.FileUploadsDirCleaner
+import mozilla.components.feature.readerview.ReaderViewMiddleware
+import mozilla.components.feature.session.SessionUseCases
+import mozilla.components.feature.tabs.TabsUseCases
+import mozilla.components.feature.session.middleware.LastAccessMiddleware
+import mozilla.components.feature.session.middleware.undo.UndoMiddleware
+import mozilla.components.feature.sitepermissions.OnDiskSitePermissionsStorage
+import mozilla.components.feature.webnotifications.WebNotificationFeature
+import mozilla.components.lib.crash.Crash
+import mozilla.components.lib.crash.CrashReporter
+import mozilla.components.lib.crash.service.CrashReporterService
+import mozilla.components.lib.dataprotect.SecureAbove22Preferences
+import mozilla.components.lib.fetch.httpurlconnection.HttpURLConnectionClient
+import mozilla.components.lib.publicsuffixlist.PublicSuffixList
+import mozilla.components.lib.state.ext.flowScoped
+import mozilla.components.service.digitalassetlinks.local.StatementApi
+import mozilla.components.service.digitalassetlinks.local.StatementRelationChecker
+import mozilla.components.support.base.android.NotificationsDelegate
+import mozilla.components.support.base.worker.Frequency
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.filterChanged
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
+import java.util.concurrent.TimeUnit
+
+private const val DAY_IN_MINUTES = 24 * 60L
+
+@SuppressLint("NewApi")
+@Suppress("LargeClass")
+open class DefaultComponents(private val applicationContext: Context) {
+ companion object {
+ const val SAMPLE_BROWSER_PREFERENCES = "sample_browser_preferences"
+ const val PREF_LAUNCH_EXTERNAL_APP = "sample_browser_launch_external_app"
+ const val PREF_GLOBAL_PRIVACY_CONTROL = "sample_browser_global_privacy_control"
+ }
+
+ val preferences: SharedPreferences =
+ applicationContext.getSharedPreferences(SAMPLE_BROWSER_PREFERENCES, Context.MODE_PRIVATE)
+
+ private val securePreferences by lazy { SecureAbove22Preferences(applicationContext, "key_store") }
+
+ val publicSuffixList by lazy { PublicSuffixList(applicationContext) }
+
+ // Engine Settings
+ val engineSettings by lazy {
+ DefaultSettings().apply {
+ //historyTrackingDelegate = HistoryDelegate(lazyHistoryStorage)
+ requestInterceptor = AppRequestInterceptor(applicationContext)
+ remoteDebuggingEnabled = true
+ supportMultipleWindows = true
+ preferredColorScheme = PreferredColorScheme.Dark
+ httpsOnlyMode = Engine.HttpsOnlyMode.ENABLED
+ globalPrivacyControlEnabled = preferences.getBoolean(
+ PREF_GLOBAL_PRIVACY_CONTROL,
+ false,
+ )
+ }
+ }
+
+ private val notificationManagerCompat = NotificationManagerCompat.from(applicationContext)
+
+ val notificationsDelegate: NotificationsDelegate by lazy {
+ NotificationsDelegate(
+ notificationManagerCompat,
+ )
+ }
+
+ val addonUpdater =
+ DefaultAddonUpdater(applicationContext, Frequency(1, TimeUnit.DAYS), notificationsDelegate)
+
+ // Engine
+ open val engine: Engine by lazy {
+ GeckoEngine(applicationContext, engineSettings)
+ }
+
+ val icons by lazy { BrowserIcons(applicationContext, client) }
+
+ open val client: Client by lazy { HttpURLConnectionClient() }
+
+ // Storage
+ //private val lazyHistoryStorage = lazy { PlacesHistoryStorage(applicationContext) }
+ //val historyStorage by lazy { lazyHistoryStorage.value }
+
+ val sessionStorage by lazy { SessionStorage(applicationContext, engine) }
+
+ val permissionStorage by lazy { OnDiskSitePermissionsStorage(applicationContext) }
+
+ val thumbnailStorage by lazy { ThumbnailStorage(applicationContext) }
+
+ val fileUploadsDirCleaner: FileUploadsDirCleaner by lazy {
+ FileUploadsDirCleaner { applicationContext.cacheDir }
+ }
+
+ val store by lazy {
+ BrowserStore(
+ middleware = listOf(
+ FlutterEventMiddleware(),
+ DownloadMiddleware(applicationContext, DownloadService::class.java),
+ ReaderViewMiddleware(),
+ ThumbnailsMiddleware(thumbnailStorage),
+ UndoMiddleware(),
+ RecordingDevicesMiddleware(applicationContext, notificationsDelegate),
+ LastAccessMiddleware(),
+ PromptMiddleware(),
+ SessionPrioritizationMiddleware(),
+ ) + EngineMiddleware.create(engine),
+ ).apply {
+ this.flowScoped { flow ->
+ flow.mapNotNull { state -> state.selectedTab }
+ .distinctUntilChangedBy {
+ it.content.url
+ }
+ .collect { tab ->
+ Log.d("URL_CHANGE", tab.content.url)
+ }
+ }
+
+ this.flowScoped { flow ->
+ flow.mapNotNull { state -> state.tabs }
+ .filterChanged {
+ it.content
+ }
+ .ifAnyChanged { arrayOf(it.content.url) }
+ .collect { tab ->
+ Log.d("URL_CHANGE2", tab.content.url)
+ }
+ }
+
+ WebNotificationFeature(
+ applicationContext,
+ engine,
+ icons,
+ R.drawable.ic_launcher_foreground,
+ permissionStorage,
+ NotificationActivity::class.java,
+ notificationsDelegate = notificationsDelegate,
+ )
+
+ MediaSessionFeature(applicationContext, MediaSessionService::class.java, this).start()
+ }
+ }
+
+ val sessionUseCases by lazy { SessionUseCases(store) }
+ val tabsUseCases by lazy { TabsUseCases(store) }
+
+ // Addons
+ val addonManager by lazy {
+ AddonManager(store, engine, addonsProvider, addonUpdater)
+ }
+
+ val addonsProvider by lazy {
+ AMOAddonsProvider(
+ applicationContext,
+ client,
+ collectionName = "7dfae8669acc4312a65e8ba5553036",
+ maxCacheAgeInMinutes = DAY_IN_MINUTES,
+ )
+ }
+
+ val supportedAddonsChecker by lazy {
+ DefaultSupportedAddonsChecker(applicationContext, Frequency(1, TimeUnit.DAYS))
+ }
+
+ val appLinksUseCases by lazy { AppLinksUseCases(applicationContext) }
+
+ val appLinksInterceptor by lazy {
+ AppLinksInterceptor(
+ applicationContext,
+ interceptLinkClicks = true,
+ launchInApp = {
+ preferences.getBoolean(PREF_LAUNCH_EXTERNAL_APP, false)
+ },
+ )
+ }
+
+ // Digital Asset Links checking
+ val relationChecker by lazy {
+ StatementRelationChecker(StatementApi(client))
+ }
+
+ val downloadsUseCases: DownloadsUseCases by lazy { DownloadsUseCases(store) }
+
+ val crashReporter: CrashReporter by lazy {
+ CrashReporter(
+ applicationContext,
+ services = listOf(
+ object : CrashReporterService {
+ override val id: String
+ get() = "xxx"
+ override val name: String
+ get() = "Test"
+
+ override fun createCrashReportUrl(identifier: String): String? {
+ return null
+ }
+
+ override fun report(crash: Crash.UncaughtExceptionCrash): String? {
+ return null
+ }
+
+ override fun report(crash: Crash.NativeCodeCrash): String? {
+ return null
+ }
+
+ override fun report(
+ throwable: Throwable,
+ breadcrumbs: ArrayList,
+ ): String? {
+ return null
+ }
+ },
+ ),
+ notificationsDelegate = notificationsDelegate,
+ ).install(applicationContext)
+ }
+}
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DownloadService.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DownloadService.kt
new file mode 100644
index 00000000..de3014e9
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/DownloadService.kt
@@ -0,0 +1,11 @@
+package eu.lensai.flutter_mozilla_components
+
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.feature.downloads.AbstractFetchDownloadService
+import mozilla.components.support.base.android.NotificationsDelegate
+
+class DownloadService : AbstractFetchDownloadService() {
+ override val httpClient by lazy { GlobalComponents.components!!.client }
+ override val store: BrowserStore by lazy { GlobalComponents.components!!.store }
+ override val notificationsDelegate: NotificationsDelegate by lazy { GlobalComponents.components!!.notificationsDelegate }
+}
\ No newline at end of file
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPlugin.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPlugin.kt
new file mode 100644
index 00000000..61359d76
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPlugin.kt
@@ -0,0 +1,136 @@
+package eu.lensai.flutter_mozilla_components
+
+import GeckoBrowserApi
+import GeckoSessionApi
+import GeckoTabsApi
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import androidx.annotation.NonNull
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import eu.lensai.flutter_mozilla_components.api.GeckoBrowserApiImpl
+import eu.lensai.flutter_mozilla_components.api.GeckoSessionApiImpl
+import eu.lensai.flutter_mozilla_components.api.GeckoTabsApiImpl
+
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.embedding.engine.plugins.activity.ActivityAware
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import mozilla.components.browser.engine.gecko.GeckoEngine
+import mozilla.components.browser.engine.gecko.fetch.GeckoViewFetchClient
+import mozilla.components.concept.engine.Engine
+import mozilla.components.experiment.NimbusExperimentDelegate
+import mozilla.components.feature.webcompat.WebCompatFeature
+import mozilla.components.lib.crash.handler.CrashHandlerService
+import mozilla.components.support.base.log.Log
+import mozilla.components.support.base.log.logger.Logger
+import mozilla.components.support.base.log.sink.AndroidLogSink
+import org.mozilla.geckoview.GeckoRuntime
+import org.mozilla.geckoview.GeckoRuntimeSettings
+
+/**
+ * Helper class for lazily instantiating components needed by the application.
+ */
+class Components(private val applicationContext: Context) : DefaultComponents(applicationContext) {
+ private val runtime by lazy {
+ // Allow for exfiltrating Gecko metrics through the Glean SDK.
+ val builder = GeckoRuntimeSettings.Builder().aboutConfigEnabled(true)
+ builder.experimentDelegate(NimbusExperimentDelegate())
+ builder.crashHandler(CrashHandlerService::class.java)
+ GeckoRuntime.create(applicationContext, builder.build())
+ }
+
+ override val engine: Engine by lazy {
+ GeckoEngine(applicationContext, engineSettings, runtime).also {
+// it.installBuiltInWebExtension("borderify@mozac.org", "resource://android/assets/extensions/borderify/") {
+// throwable ->
+// Log.log(Log.Priority.ERROR, "SampleBrowser", throwable, "Failed to install borderify")
+// }
+//
+// it.installBuiltInWebExtension("testext@mozac.org", "resource://android/assets/extensions/test/") {
+// throwable ->
+// Log.log(Log.Priority.ERROR, "SampleBrowser", throwable, "Failed to install testext")
+// }
+
+ WebCompatFeature.install(it)
+ //WebCompatReporterFeature.install(it)
+ }
+ }
+
+ override val client by lazy { GeckoViewFetchClient(applicationContext, runtime) }
+}
+
+
+/** FlutterMozillaComponentsPlugin */
+class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
+ private var activity: Activity? = null
+
+ private lateinit var _flutterPluginBinding: FlutterPlugin.FlutterPluginBinding;
+
+ init {
+ Log.addSink(AndroidLogSink())
+ }
+
+ override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+ _flutterPluginBinding = flutterPluginBinding
+
+ GlobalComponents.setUp(flutterPluginBinding.applicationContext)
+
+ val intent = Intent(flutterPluginBinding.applicationContext, NotificationActivity::class.java)
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ flutterPluginBinding.applicationContext.startActivity(intent)
+
+ GeckoBrowserApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoBrowserApiImpl {
+ showNativeFragment()
+ })
+
+ GeckoSessionApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoSessionApiImpl())
+ GeckoTabsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoTabsApiImpl())
+ }
+
+ private fun showNativeFragment() {
+ if (activity == null) {
+ //result.error("ACTIVITY_NOT_ATTACHED", "Activity is not attached", null)
+ return
+ }
+
+ // Replace this with your actual Fragment
+ val nativeFragment = BrowserFragment.create(activity!!)
+
+ val fm = (activity as FragmentActivity).supportFragmentManager
+ fm.beginTransaction()
+ .replace(FRAGMENT_CONTAINER_ID, nativeFragment)
+ .commitAllowingStateLoss()
+
+ }
+
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+
+ }
+
+ override fun onAttachedToActivity(binding: ActivityPluginBinding) {
+ this.activity = binding.activity
+
+ _flutterPluginBinding.platformViewRegistry.registerViewFactory(
+ "eu.lensai/gecko", GeckoViewFactory(binding.activity, FRAGMENT_CONTAINER_ID))
+ }
+
+ override fun onDetachedFromActivityForConfigChanges() {
+ onDetachedFromActivity()
+ }
+
+ override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
+ onAttachedToActivity(binding)
+ }
+
+ override fun onDetachedFromActivity() {
+ this.activity = null
+ }
+
+ companion object {
+ private const val FRAGMENT_CONTAINER_ID = 0xBEEF
+ }
+}
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GeckoPlatformView.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GeckoPlatformView.kt
new file mode 100644
index 00000000..b8b860c9
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GeckoPlatformView.kt
@@ -0,0 +1,42 @@
+package eu.lensai.flutter_mozilla_components
+
+import android.view.View
+import android.app.Activity
+import android.content.Context
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import io.flutter.plugin.common.StandardMessageCodec
+import io.flutter.plugin.platform.PlatformView
+import io.flutter.plugin.platform.PlatformViewFactory
+
+class GeckoViewFactory(private val activity: Activity, private val containerId: Int) : PlatformViewFactory(
+ StandardMessageCodec.INSTANCE) {
+ override fun create(context: Context?, id: Int, args: Any?): PlatformView {
+ return NativeFragmentView(this.activity, this.containerId)
+ }
+}
+
+private class NativeFragmentView(
+ private val activity: Activity?,
+ private val containerId: Int
+) : PlatformView {
+ private val container: View
+
+ init {
+ val vParams: ViewGroup.LayoutParams =
+ FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ container = FrameLayout(activity!!)
+ container.layoutParams = vParams
+ container.id = containerId
+ }
+
+ override fun getView(): View {
+ return container
+ }
+
+ override fun dispose() {
+ // Clean up if needed
+ }
+}
\ No newline at end of file
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GlobalComponents.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GlobalComponents.kt
new file mode 100644
index 00000000..8788b51b
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/GlobalComponents.kt
@@ -0,0 +1,78 @@
+package eu.lensai.flutter_mozilla_components
+
+import android.content.Context
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import mozilla.components.feature.addons.update.GlobalAddonDependencyProvider
+import mozilla.components.support.base.facts.Facts
+import mozilla.components.support.base.facts.processor.LogFactProcessor
+import mozilla.components.support.base.log.logger.Logger
+import mozilla.components.support.webextensions.WebExtensionSupport
+import java.util.concurrent.TimeUnit
+
+object GlobalComponents {
+ private var _components: Components? = null
+
+ val components: Components?
+ get() = _components
+
+ @DelicateCoroutinesApi
+ private fun restoreBrowserState(newComponents: Components) = GlobalScope.launch(Dispatchers.Main) {
+ newComponents.tabsUseCases.restore(newComponents.sessionStorage)
+
+ newComponents.sessionStorage.autoSave(newComponents.store)
+ .periodicallyInForeground(interval = 30, unit = TimeUnit.SECONDS)
+ .whenGoingToBackground()
+ .whenSessionsChange()
+ }
+
+ fun setUp(applicationContext: Context) {
+ val newComponents = Components(applicationContext)
+
+ newComponents.crashReporter.install(applicationContext)
+ Facts.registerProcessor(LogFactProcessor())
+
+ newComponents.engine.warmUp()
+
+ restoreBrowserState(newComponents)
+ newComponents.downloadsUseCases.restoreDownloads()
+
+ try {
+ GlobalAddonDependencyProvider.initialize(
+ newComponents.addonManager,
+ newComponents.addonUpdater,
+ )
+
+ WebExtensionSupport.initialize(
+ newComponents.engine,
+ newComponents.store,
+ onNewTabOverride = {
+ _, engineSession, url ->
+ newComponents.tabsUseCases.addTab(url, selectTab = true, engineSession = engineSession)
+ },
+ onCloseTabOverride = {
+ _, sessionId ->
+ newComponents.tabsUseCases.removeTab(sessionId)
+ },
+ onSelectTabOverride = {
+ _, sessionId ->
+ newComponents.tabsUseCases.selectTab(sessionId)
+ },
+ onUpdatePermissionRequest = newComponents.addonUpdater::onUpdatePermissionRequest,
+ onExtensionsLoaded = { extensions ->
+ newComponents.addonUpdater.registerForFutureUpdates(extensions)
+ newComponents.supportedAddonsChecker.registerForChecks()
+ },
+ )
+ } catch (e: UnsupportedOperationException) {
+ // Web extension support is only available for engine gecko
+ Logger.error("Failed to initialize web extension support", e)
+ }
+
+ newComponents.tabsUseCases.addTab.invoke("https://google.com", selectTab = true)
+
+ _components = newComponents
+ }
+}
\ No newline at end of file
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/IntentReceiverActivity.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/IntentReceiverActivity.kt
new file mode 100644
index 00000000..073d2942
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/IntentReceiverActivity.kt
@@ -0,0 +1,2 @@
+package eu.lensai.flutter_mozilla_components
+
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/MediaSessionService.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/MediaSessionService.kt
new file mode 100644
index 00000000..e2909b61
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/MediaSessionService.kt
@@ -0,0 +1,15 @@
+package eu.lensai.flutter_mozilla_components
+
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.concept.base.crash.CrashReporting
+import mozilla.components.feature.media.service.AbstractMediaSessionService
+import mozilla.components.support.base.android.NotificationsDelegate
+
+/**
+ * See [AbstractMediaSessionService].
+ */
+class MediaSessionService : AbstractMediaSessionService() {
+ override val crashReporter: CrashReporting? by lazy { GlobalComponents.components!!.crashReporter }
+ override val store: BrowserStore by lazy { GlobalComponents.components!!.store }
+ override val notificationsDelegate: NotificationsDelegate by lazy { GlobalComponents.components!!.notificationsDelegate }
+}
\ No newline at end of file
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/NotificationActivity.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/NotificationActivity.kt
new file mode 100644
index 00000000..99c7c7c5
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/NotificationActivity.kt
@@ -0,0 +1,15 @@
+package eu.lensai.flutter_mozilla_components
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import eu.lensai.flutter_mozilla_components.GlobalComponents
+
+class NotificationActivity: AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ GlobalComponents.components!!.notificationsDelegate.bindToActivity(this)
+
+ finish()
+ }
+}
\ No newline at end of file
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt
new file mode 100644
index 00000000..9104f1b7
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoBrowserApiImpl.kt
@@ -0,0 +1,11 @@
+package eu.lensai.flutter_mozilla_components.api
+
+import GeckoBrowserApi
+
+class GeckoBrowserApiImpl(
+ private val showFragmentCallback: () -> Unit
+) : GeckoBrowserApi {
+ override fun showNativeFragment() {
+ showFragmentCallback.invoke();
+ }
+}
\ No newline at end of file
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoSessionApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoSessionApiImpl.kt
new file mode 100644
index 00000000..9f4a96d7
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoSessionApiImpl.kt
@@ -0,0 +1,137 @@
+package eu.lensai.flutter_mozilla_components.api
+
+import GeckoSessionApi
+import LoadUrlFlagsValue
+import android.util.Log
+import eu.lensai.flutter_mozilla_components.GlobalComponents
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.concept.engine.EngineSession
+import mozilla.components.concept.engine.translate.TranslationOptions
+import TranslationOptions as PigeonTranslationOptions
+import mozilla.components.feature.session.SessionUseCases
+
+class GeckoSessionApiImpl() : GeckoSessionApi {
+ private val sessionUseCases: SessionUseCases by lazy { GlobalComponents.components!!.sessionUseCases }
+ private val state: BrowserState by lazy { GlobalComponents.components!!.store.state }
+
+ override fun loadUrl(
+ tabId: String?,
+ url: String,
+ flags: LoadUrlFlagsValue,
+ additionalHeaders: Map?
+ ) {
+ sessionUseCases.loadUrl(
+ url = url,
+ sessionId = tabId,
+ flags = EngineSession.LoadUrlFlags.select(flags.value.toInt()),
+ additionalHeaders = additionalHeaders
+ )
+ }
+
+ override fun loadData(tabId: String?, data: String, mimeType: String, encoding: String) {
+ sessionUseCases.loadData(
+ data = data,
+ tabId = tabId ?: state.selectedTabId,
+ mimeType = mimeType,
+ encoding = encoding,
+ )
+ }
+
+ override fun reload(tabId: String?, flags: LoadUrlFlagsValue) {
+ sessionUseCases.reload(
+ tabId = tabId ?: state.selectedTabId,
+ flags = EngineSession.LoadUrlFlags.select(flags.value.toInt()),
+ )
+ }
+
+ override fun stopLoading(tabId: String?) {
+ sessionUseCases.stopLoading(
+ tabId = tabId
+ )
+ }
+
+ override fun goBack(tabId: String?, userInteraction: Boolean) {
+ sessionUseCases.goBack(
+ tabId = tabId ?: state.selectedTabId,
+ userInteraction = userInteraction
+ )
+ }
+
+ override fun goForward(tabId: String?, userInteraction: Boolean) {
+ sessionUseCases.goForward(
+ tabId = tabId ?: state.selectedTabId,
+ userInteraction = userInteraction
+ )
+ }
+
+ override fun goToHistoryIndex(index: Long, tabId: String?) {
+ sessionUseCases.goToHistoryIndex(
+ tabId = tabId ?: state.selectedTabId,
+ index = index.toInt()
+ )
+ }
+
+ override fun requestDesktopSite(tabId: String?, enable: Boolean) {
+ sessionUseCases.requestDesktopSite(
+ tabId = tabId ?: state.selectedTabId,
+ enable = enable,
+ )
+ }
+
+ override fun exitFullscreen(tabId: String?) {
+ sessionUseCases.exitFullscreen(
+ tabId = tabId ?: state.selectedTabId,
+ )
+ }
+
+ override fun saveToPdf(tabId: String?) {
+ sessionUseCases.saveToPdf(
+ tabId = tabId ?: state.selectedTabId,
+ )
+ }
+
+ override fun printContent(tabId: String?) {
+ sessionUseCases.printContent(
+ tabId = tabId ?: state.selectedTabId,
+ )
+ }
+
+ override fun translate(
+ tabId: String?,
+ fromLanguage: String,
+ toLanguage: String,
+ options: PigeonTranslationOptions?
+ ) {
+ sessionUseCases.translate(
+ tabId = tabId ?: state.selectedTabId,
+ fromLanguage = fromLanguage,
+ toLanguage = toLanguage,
+ options = if (options != null) TranslationOptions(downloadModel = options.downloadModel) else null,
+ )
+ }
+
+ override fun translateRestore(tabId: String?) {
+ sessionUseCases.translateRestore(
+ tabId = tabId ?: state.selectedTabId,
+ )
+ }
+
+ override fun crashRecovery(tabIds: List?) {
+ if(tabIds != null) {
+ sessionUseCases.crashRecovery.invoke(tabIds = tabIds)
+ } else {
+ sessionUseCases.crashRecovery.invoke()
+ }
+ }
+
+ override fun purgeHistory() {
+ sessionUseCases.purgeHistory()
+ }
+
+ override fun updateLastAccess(tabId: String?, lastAccess: Long?) {
+ sessionUseCases.updateLastAccess(
+ tabId = tabId ?: state.selectedTabId,
+ lastAccess = lastAccess ?: System.currentTimeMillis()
+ )
+ }
+}
\ No newline at end of file
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoTabsApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoTabsApiImpl.kt
new file mode 100644
index 00000000..9b97cb02
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoTabsApiImpl.kt
@@ -0,0 +1,221 @@
+package eu.lensai.flutter_mozilla_components.api
+
+import GeckoTabsApi
+import HistoryMetadataKey as PigeonHistoryMetadataKey
+import LoadUrlFlagsValue
+import RecoverableBrowserState as PigeonRecoverableBrowserState
+import RestoreLocation as PigeonRestoreLocation
+import RecoverableTab as PigeonRecoverableTab
+import SourceValue
+import eu.lensai.flutter_mozilla_components.GlobalComponents
+import mozilla.components.browser.session.storage.RecoverableBrowserState
+import mozilla.components.browser.state.action.TabListAction
+import mozilla.components.browser.state.selector.findTab
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.browser.state.state.LastMediaAccessState
+import mozilla.components.browser.state.state.ReaderState
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.browser.state.state.recover.RecoverableTab
+import mozilla.components.browser.state.state.recover.TabState
+import mozilla.components.concept.engine.Engine
+import mozilla.components.concept.engine.EngineSession
+import mozilla.components.concept.storage.HistoryMetadataKey
+import mozilla.components.feature.tabs.TabsUseCases
+import org.json.JSONObject
+
+class GeckoTabsApiImpl() : GeckoTabsApi {
+ private val tabsUseCases: TabsUseCases by lazy { GlobalComponents.components!!.tabsUseCases }
+ private val engine: Engine by lazy { GlobalComponents.components!!.engine }
+ private val state: BrowserState by lazy { GlobalComponents.components!!.store.state }
+
+ private fun restoreSource(source: SourceValue ) : SessionState.Source {
+ return SessionState.Source.restore(
+ source.id.toInt(),
+ source.caller?.packageId,
+ source.caller?.category?.value?.toInt()
+ )
+ }
+ private fun mapTab(t: PigeonRecoverableTab) : RecoverableTab {
+ return RecoverableTab(
+ engineSessionState = if(t.engineSessionStateJson != null)
+ engine.createSessionState(JSONObject(t.engineSessionStateJson))
+ else
+ null,
+ state = TabState(
+ id = t.state.id,
+ url = t.state.url,
+ parentId = t.state.parentId,
+ title = t.state.title,
+ searchTerm = t.state.searchTerm,
+ contextId = t.state.contextId,
+ readerState = ReaderState(
+ readerable = t.state.readerState.readerable,
+ active = t.state.readerState.active,
+ checkRequired = t.state.readerState.checkRequired,
+ connectRequired = t.state.readerState.connectRequired,
+ baseUrl = t.state.readerState.baseUrl,
+ activeUrl = t.state.readerState.activeUrl,
+ scrollY = t.state.readerState.scrollY?.toInt(),
+ ),
+ lastAccess = t.state.lastAccess,
+ createdAt = t.state.createdAt,
+ lastMediaAccessState = LastMediaAccessState(
+ lastMediaUrl = t.state.lastMediaAccessState.lastMediaUrl,
+ lastMediaAccess = t.state.lastMediaAccessState.lastMediaAccess,
+ mediaSessionActive = t.state.lastMediaAccessState.mediaSessionActive
+ ),
+ private = t.state.private,
+ historyMetadata = if(t.state.historyMetadata != null)
+ HistoryMetadataKey(
+ url = t.state.historyMetadata.url,
+ searchTerm = t.state.historyMetadata.searchTerm,
+ referrerUrl = t.state.historyMetadata.referrerUrl
+ )
+ else null,
+ source = restoreSource(t.state.source),
+ index = t.state.index.toInt(),
+ hasFormData = t.state.hasFormData,
+ )
+ )
+ }
+
+ private fun mapRestoreLocation(t: PigeonRestoreLocation) : TabListAction.RestoreAction.RestoreLocation {
+ return TabListAction.RestoreAction.RestoreLocation.entries.first { it.ordinal == t.ordinal }
+ }
+
+ override fun selectTab(tabId: String) {
+ tabsUseCases.selectTab(tabId = tabId)
+ }
+
+ override fun removeTab(tabId: String) {
+ tabsUseCases.removeTab(tabId = tabId)
+ }
+
+ override fun addTab(
+ url: String,
+ selectTab: Boolean,
+ startLoading: Boolean,
+ parentId: String?,
+ flags: LoadUrlFlagsValue,
+ contextId: String?,
+ source: SourceValue,
+ private: Boolean,
+ historyMetadata: PigeonHistoryMetadataKey?,
+ additionalHeaders: Map?
+ ): String {
+ return tabsUseCases.addTab(
+ url = url,
+ selectTab = selectTab,
+ startLoading = startLoading,
+ parentId = parentId,
+ flags = EngineSession.LoadUrlFlags.select(flags.value.toInt()),
+ contextId = contextId,
+ source = restoreSource(source),
+ private = private,
+ historyMetadata = if(historyMetadata != null)
+ HistoryMetadataKey(
+ url = historyMetadata.url,
+ searchTerm = historyMetadata.searchTerm,
+ referrerUrl = historyMetadata.referrerUrl
+ )
+ else null,
+ additionalHeaders = additionalHeaders
+ )
+ }
+
+ override fun removeAllTabs(recoverable: Boolean) {
+ tabsUseCases.removeAllTabs(recoverable = recoverable)
+ }
+
+ override fun removeTabs(ids: List) {
+ tabsUseCases.removeTabs(ids = ids)
+ }
+
+ override fun removeNormalTabs() {
+ tabsUseCases.removeNormalTabs()
+ }
+
+ override fun removePrivateTabs() {
+ tabsUseCases.removePrivateTabs()
+ }
+
+ override fun undo() {
+ tabsUseCases.undo()
+ }
+
+ override fun restoreTabsByList(
+ tabs: List,
+ selectTabId: String?,
+ restoreLocation: PigeonRestoreLocation
+ ) {
+ tabsUseCases.restore(
+ tabs = tabs.map { t -> mapTab(t) },
+ restoreLocation = mapRestoreLocation(restoreLocation),
+ selectTabId = selectTabId,
+ )
+ }
+
+ override fun restoreTabsByBrowserState(
+ state: PigeonRecoverableBrowserState,
+ restoreLocation: PigeonRestoreLocation
+ ) {
+ tabsUseCases.restore(
+ state = RecoverableBrowserState(
+ selectedTabId = state.selectedTabId,
+ tabs = state.tabs.filterNotNull().map { t -> mapTab(t) },
+ ),
+ restoreLocation = mapRestoreLocation(restoreLocation),
+ )
+ }
+
+ override fun selectOrAddTabByHistory(url: String, historyMetadata: PigeonHistoryMetadataKey): String {
+ return tabsUseCases.selectOrAddTab(
+ url = url,
+ historyMetadata = HistoryMetadataKey(
+ url = historyMetadata.url,
+ searchTerm = historyMetadata.searchTerm,
+ referrerUrl = historyMetadata.referrerUrl
+ ),
+ )
+ }
+
+ override fun selectOrAddTabByUrl(
+ url: String,
+ private: Boolean,
+ source: SourceValue,
+ flags: LoadUrlFlagsValue,
+ ignoreFragment: Boolean
+ ): String {
+ return tabsUseCases.selectOrAddTab(
+ url = url,
+ private = private,
+ source = restoreSource(source),
+ flags = EngineSession.LoadUrlFlags.select(flags.value.toInt()),
+ ignoreFragment = ignoreFragment,
+ )
+ }
+
+ override fun duplicateTab(selectTabId: String?, selectNewTab: Boolean): String {
+ val tabState = if(selectTabId != null) state.findTab(selectTabId) else null
+
+ return tabsUseCases.duplicateTab(
+ tab = tabState!!,
+ selectNewTab = selectNewTab,
+ )
+ }
+
+ override fun moveTabs(tabIds: List, targetTabId: String, placeAfter: Boolean) {
+ tabsUseCases.moveTabs(
+ tabIds = tabIds,
+ targetTabId = targetTabId,
+ placeAfter = placeAfter,
+ )
+ }
+
+ override fun migratePrivateTabUseCase(tabId: String, alternativeUrl: String?): String {
+ return tabsUseCases.migratePrivateTabUseCase(
+ tabId = tabId,
+ alternativeUrl = alternativeUrl
+ )
+ }
+}
\ No newline at end of file
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/middleware/FlutterEventMiddleware.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/middleware/FlutterEventMiddleware.kt
new file mode 100644
index 00000000..33e552e2
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/middleware/FlutterEventMiddleware.kt
@@ -0,0 +1,37 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+package eu.lensai.flutter_mozilla_components.middleware
+
+import android.util.Log
+import mozilla.components.browser.state.action.BrowserAction
+import mozilla.components.browser.state.action.ContentAction
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.MiddlewareContext
+
+/**
+ * [Middleware] implementation for handling [ContentAction.UpdateThumbnailAction] and storing
+ * the thumbnail to the disk cache.
+ */
+class FlutterEventMiddleware() : Middleware {
+ @Suppress("ComplexMethod")
+ override fun invoke(
+ context: MiddlewareContext,
+ next: (BrowserAction) -> Unit,
+ action: BrowserAction,
+ ) {
+ when (action) {
+ is ContentAction.UpdateThumbnailAction -> {
+ Log.d("UpdateThumbnailAction", action.sessionId)
+ }
+ is ContentAction.UpdateTitleAction -> {
+ Log.d("UpdateTitleAction", action.title)
+ }
+ else -> {
+ // no-op
+ }
+ }
+ next(action)
+ }
+}
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt
new file mode 100644
index 00000000..e28aa834
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt
@@ -0,0 +1,1258 @@
+// Autogenerated from Pigeon (v22.3.0), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
+
+
+import android.util.Log
+import io.flutter.plugin.common.BasicMessageChannel
+import io.flutter.plugin.common.BinaryMessenger
+import io.flutter.plugin.common.MessageCodec
+import io.flutter.plugin.common.StandardMessageCodec
+import java.io.ByteArrayOutputStream
+import java.nio.ByteBuffer
+
+private fun wrapResult(result: Any?): List {
+ return listOf(result)
+}
+
+private fun wrapError(exception: Throwable): List {
+ return if (exception is FlutterError) {
+ listOf(
+ exception.code,
+ exception.message,
+ exception.details
+ )
+ } else {
+ listOf(
+ exception.javaClass.simpleName,
+ exception.toString(),
+ "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
+ )
+ }
+}
+
+/**
+ * Error class for passing custom error details to Flutter via a thrown PlatformException.
+ * @property code The error code.
+ * @property message The error message.
+ * @property details The error details. Must be a datatype supported by the api codec.
+ */
+class FlutterError (
+ val code: String,
+ override val message: String? = null,
+ val details: Any? = null
+) : Throwable()
+
+/** Indicates what location the tabs should be restored at */
+enum class RestoreLocation(val raw: Int) {
+ /** Restore tabs at the beginning of the tab list */
+ BEGINNING(0),
+ /** Restore tabs at the end of the tab list */
+ END(1),
+ /** Restore tabs at a specific index in the tab list */
+ AT_INDEX(2);
+
+ companion object {
+ fun ofRaw(raw: Int): RestoreLocation? {
+ return values().firstOrNull { it.raw == raw }
+ }
+ }
+}
+
+/**
+ * Translation options that map to the Gecko Translations Options.
+ *
+ * @property downloadModel If the necessary models should be downloaded on request. If false, then
+ * the translation will not complete and throw an exception if the models are not already available.
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
+data class TranslationOptions (
+ val downloadModel: Boolean
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): TranslationOptions {
+ val downloadModel = pigeonVar_list[0] as Boolean
+ return TranslationOptions(downloadModel)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ downloadModel,
+ )
+ }
+}
+
+/**
+ * Value type that represents the state of reader mode/view.
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
+data class ReaderState (
+ /**
+ * Whether or not the current page can be transformed to
+ * be displayed in a reader view.
+ */
+ val readerable: Boolean,
+ /** Whether or not reader view is active. */
+ val active: Boolean,
+ /**
+ * Whether or not a readerable check is required for the
+ * current page.
+ */
+ val checkRequired: Boolean,
+ /**
+ * Whether or not a new connection to the reader view
+ * content script is required.
+ */
+ val connectRequired: Boolean,
+ /** The base URL of the reader view extension page. */
+ val baseUrl: String? = null,
+ /** The URL of the page currently displayed in reader view. */
+ val activeUrl: String? = null,
+ /**
+ * The vertical scroll position of the page currently
+ * displayed in reader view.
+ */
+ val scrollY: Long? = null
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): ReaderState {
+ val readerable = pigeonVar_list[0] as Boolean
+ val active = pigeonVar_list[1] as Boolean
+ val checkRequired = pigeonVar_list[2] as Boolean
+ val connectRequired = pigeonVar_list[3] as Boolean
+ val baseUrl = pigeonVar_list[4] as String?
+ val activeUrl = pigeonVar_list[5] as String?
+ val scrollY = pigeonVar_list[6] as Long?
+ return ReaderState(readerable, active, checkRequired, connectRequired, baseUrl, activeUrl, scrollY)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ readerable,
+ active,
+ checkRequired,
+ connectRequired,
+ baseUrl,
+ activeUrl,
+ scrollY,
+ )
+ }
+}
+
+/**
+ * Details about the last playing media in this tab.
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
+data class LastMediaAccessState (
+ /**
+ * [ContentState.url] when media started playing.
+ * This is not the URL of the media but of the page when media started.
+ * Defaults to "" (an empty String) if media hasn't started playing.
+ * This value is only updated when media starts playing.
+ * Can be used as a backup to [mediaSessionActive] for knowing the user is still on the same website
+ * on which media was playing before media started playing in another tab.
+ */
+ val lastMediaUrl: String,
+ /**
+ * The last time media started playing in the current web document.
+ * Defaults to [0] if media hasn't started playing.
+ * This value is only updated when media starts playing.
+ */
+ val lastMediaAccess: Long,
+ /**
+ * Whether or not the last accessed media is still active.
+ * Can be used as a backup to [lastMediaUrl] on websites which allow media to continue playing
+ * even when the users accesses another page (with another URL) in that same HTML document.
+ */
+ val mediaSessionActive: Boolean
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): LastMediaAccessState {
+ val lastMediaUrl = pigeonVar_list[0] as String
+ val lastMediaAccess = pigeonVar_list[1] as Long
+ val mediaSessionActive = pigeonVar_list[2] as Boolean
+ return LastMediaAccessState(lastMediaUrl, lastMediaAccess, mediaSessionActive)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ lastMediaUrl,
+ lastMediaAccess,
+ mediaSessionActive,
+ )
+ }
+}
+
+/**
+ * Represents a set of history metadata values that uniquely identify a record. Note that
+ * when recording observations, the same set of values may or may not cause a new record to be
+ * created, depending on the de-bouncing logic of the underlying storage i.e. recording history
+ * metadata observations with the exact same values may be combined into a single record.
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
+data class HistoryMetadataKey (
+ /** A url of the page. */
+ val url: String,
+ /**
+ * An optional search term if this record was
+ * created as part of a search by the user.
+ */
+ val searchTerm: String? = null,
+ /**
+ * An optional url of the parent/referrer if
+ * this record was created in response to a user opening
+ * a page in a new tab.
+ */
+ val referrerUrl: String? = null
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): HistoryMetadataKey {
+ val url = pigeonVar_list[0] as String
+ val searchTerm = pigeonVar_list[1] as String?
+ val referrerUrl = pigeonVar_list[2] as String?
+ return HistoryMetadataKey(url, searchTerm, referrerUrl)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ url,
+ searchTerm,
+ referrerUrl,
+ )
+ }
+}
+
+/** Generated class from Pigeon that represents data sent in messages. */
+data class PackageCategoryValue (
+ val value: Long
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): PackageCategoryValue {
+ val value = pigeonVar_list[0] as Long
+ return PackageCategoryValue(value)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ value,
+ )
+ }
+}
+
+/**
+ * Describes an external package.
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
+data class ExternalPackage (
+ /** An Android package id. */
+ val packageId: String,
+ /** A [PackageCategory] as defined by the application. */
+ val category: PackageCategoryValue
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): ExternalPackage {
+ val packageId = pigeonVar_list[0] as String
+ val category = pigeonVar_list[1] as PackageCategoryValue
+ return ExternalPackage(packageId, category)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ packageId,
+ category,
+ )
+ }
+}
+
+/** Generated class from Pigeon that represents data sent in messages. */
+data class LoadUrlFlagsValue (
+ val value: Long
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): LoadUrlFlagsValue {
+ val value = pigeonVar_list[0] as Long
+ return LoadUrlFlagsValue(value)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ value,
+ )
+ }
+}
+
+/** Generated class from Pigeon that represents data sent in messages. */
+data class SourceValue (
+ val id: Long,
+ val caller: ExternalPackage? = null
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): SourceValue {
+ val id = pigeonVar_list[0] as Long
+ val caller = pigeonVar_list[1] as ExternalPackage?
+ return SourceValue(id, caller)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ id,
+ caller,
+ )
+ }
+}
+
+/**
+ * A tab that is no longer open and in the list of tabs, but that can be restored (recovered) at
+ * any time if it's combined with an [EngineSessionState] to form a [RecoverableTab].
+ *
+ * The values of this data class are usually filled with the values of a [TabSessionState] when
+ * getting closed.
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
+data class TabState (
+ /** Unique ID identifying this tab. */
+ val id: String,
+ /** The last URL of this tab. */
+ val url: String,
+ /**
+ * The unique ID of the parent tab if this tab was opened from another tab (e.g. via
+ * the context menu).
+ */
+ val parentId: String? = null,
+ /** The last title of this tab (or an empty String). */
+ val title: String,
+ /**
+ * The last used search terms, or an empty string if no
+ * search was executed for this session.
+ */
+ val searchTerm: String,
+ /** The context ID ("container") this tab used (or null). */
+ val contextId: String? = null,
+ /** The last [ReaderState] of the tab. */
+ val readerState: ReaderState,
+ /** The last time this tab was selected. */
+ val lastAccess: Long,
+ /** Timestamp of the tab's creation. */
+ val createdAt: Long,
+ /** Details about the last time was playing in this tab. */
+ val lastMediaAccessState: LastMediaAccessState,
+ /** If tab was private. */
+ val private: Boolean,
+ /** The last [HistoryMetadataKey] of the tab. */
+ val historyMetadata: HistoryMetadataKey? = null,
+ /** The last [Source] of the tab. */
+ val source: SourceValue,
+ /** The index the tab should be restored at. */
+ val index: Long,
+ /** Whether the tab has form data. */
+ val hasFormData: Boolean
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): TabState {
+ val id = pigeonVar_list[0] as String
+ val url = pigeonVar_list[1] as String
+ val parentId = pigeonVar_list[2] as String?
+ val title = pigeonVar_list[3] as String
+ val searchTerm = pigeonVar_list[4] as String
+ val contextId = pigeonVar_list[5] as String?
+ val readerState = pigeonVar_list[6] as ReaderState
+ val lastAccess = pigeonVar_list[7] as Long
+ val createdAt = pigeonVar_list[8] as Long
+ val lastMediaAccessState = pigeonVar_list[9] as LastMediaAccessState
+ val private = pigeonVar_list[10] as Boolean
+ val historyMetadata = pigeonVar_list[11] as HistoryMetadataKey?
+ val source = pigeonVar_list[12] as SourceValue
+ val index = pigeonVar_list[13] as Long
+ val hasFormData = pigeonVar_list[14] as Boolean
+ return TabState(id, url, parentId, title, searchTerm, contextId, readerState, lastAccess, createdAt, lastMediaAccessState, private, historyMetadata, source, index, hasFormData)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ id,
+ url,
+ parentId,
+ title,
+ searchTerm,
+ contextId,
+ readerState,
+ lastAccess,
+ createdAt,
+ lastMediaAccessState,
+ private,
+ historyMetadata,
+ source,
+ index,
+ hasFormData,
+ )
+ }
+}
+
+/**
+ * A recoverable version of [TabState].
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
+data class RecoverableTab (
+ /** The [EngineSessionState] needed for restoring the previous state of this tab. */
+ val engineSessionStateJson: String? = null,
+ /** A [TabState] instance containing basic tab state. */
+ val state: TabState
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): RecoverableTab {
+ val engineSessionStateJson = pigeonVar_list[0] as String?
+ val state = pigeonVar_list[1] as TabState
+ return RecoverableTab(engineSessionStateJson, state)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ engineSessionStateJson,
+ state,
+ )
+ }
+}
+
+/**
+ * A restored browser state, read from disk.
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
+data class RecoverableBrowserState (
+ /** The list of restored tabs. */
+ val tabs: List,
+ /** The ID of the selected tab in [tabs]. Or `null` if no selection was restored. */
+ val selectedTabId: String? = null
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): RecoverableBrowserState {
+ val tabs = pigeonVar_list[0] as List
+ val selectedTabId = pigeonVar_list[1] as String?
+ return RecoverableBrowserState(tabs, selectedTabId)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ tabs,
+ selectedTabId,
+ )
+ }
+}
+private open class GeckoPigeonCodec : StandardMessageCodec() {
+ override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
+ return when (type) {
+ 129.toByte() -> {
+ return (readValue(buffer) as Long?)?.let {
+ RestoreLocation.ofRaw(it.toInt())
+ }
+ }
+ 130.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ TranslationOptions.fromList(it)
+ }
+ }
+ 131.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ ReaderState.fromList(it)
+ }
+ }
+ 132.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ LastMediaAccessState.fromList(it)
+ }
+ }
+ 133.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ HistoryMetadataKey.fromList(it)
+ }
+ }
+ 134.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ PackageCategoryValue.fromList(it)
+ }
+ }
+ 135.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ ExternalPackage.fromList(it)
+ }
+ }
+ 136.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ LoadUrlFlagsValue.fromList(it)
+ }
+ }
+ 137.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ SourceValue.fromList(it)
+ }
+ }
+ 138.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ TabState.fromList(it)
+ }
+ }
+ 139.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ RecoverableTab.fromList(it)
+ }
+ }
+ 140.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ RecoverableBrowserState.fromList(it)
+ }
+ }
+ else -> super.readValueOfType(type, buffer)
+ }
+ }
+ override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
+ when (value) {
+ is RestoreLocation -> {
+ stream.write(129)
+ writeValue(stream, value.raw)
+ }
+ is TranslationOptions -> {
+ stream.write(130)
+ writeValue(stream, value.toList())
+ }
+ is ReaderState -> {
+ stream.write(131)
+ writeValue(stream, value.toList())
+ }
+ is LastMediaAccessState -> {
+ stream.write(132)
+ writeValue(stream, value.toList())
+ }
+ is HistoryMetadataKey -> {
+ stream.write(133)
+ writeValue(stream, value.toList())
+ }
+ is PackageCategoryValue -> {
+ stream.write(134)
+ writeValue(stream, value.toList())
+ }
+ is ExternalPackage -> {
+ stream.write(135)
+ writeValue(stream, value.toList())
+ }
+ is LoadUrlFlagsValue -> {
+ stream.write(136)
+ writeValue(stream, value.toList())
+ }
+ is SourceValue -> {
+ stream.write(137)
+ writeValue(stream, value.toList())
+ }
+ is TabState -> {
+ stream.write(138)
+ writeValue(stream, value.toList())
+ }
+ is RecoverableTab -> {
+ stream.write(139)
+ writeValue(stream, value.toList())
+ }
+ is RecoverableBrowserState -> {
+ stream.write(140)
+ writeValue(stream, value.toList())
+ }
+ else -> super.writeValue(stream, value)
+ }
+ }
+}
+
+/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
+interface GeckoBrowserApi {
+ fun showNativeFragment()
+
+ companion object {
+ /** The codec used by GeckoBrowserApi. */
+ val codec: MessageCodec by lazy {
+ GeckoPigeonCodec()
+ }
+ /** Sets up an instance of `GeckoBrowserApi` to handle messages through the `binaryMessenger`. */
+ @JvmOverloads
+ fun setUp(binaryMessenger: BinaryMessenger, api: GeckoBrowserApi?, messageChannelSuffix: String = "") {
+ val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoBrowserApi.showNativeFragment$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { _, reply ->
+ val wrapped: List = try {
+ api.showNativeFragment()
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ }
+ }
+}
+/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
+interface GeckoSessionApi {
+ fun loadUrl(tabId: String?, url: String, flags: LoadUrlFlagsValue, additionalHeaders: Map?)
+ fun loadData(tabId: String?, data: String, mimeType: String, encoding: String)
+ fun reload(tabId: String?, flags: LoadUrlFlagsValue)
+ fun stopLoading(tabId: String?)
+ fun goBack(tabId: String?, userInteraction: Boolean)
+ fun goForward(tabId: String?, userInteraction: Boolean)
+ fun goToHistoryIndex(index: Long, tabId: String?)
+ fun requestDesktopSite(tabId: String?, enable: Boolean)
+ fun exitFullscreen(tabId: String?)
+ fun saveToPdf(tabId: String?)
+ fun printContent(tabId: String?)
+ fun translate(tabId: String?, fromLanguage: String, toLanguage: String, options: TranslationOptions?)
+ fun translateRestore(tabId: String?)
+ fun crashRecovery(tabIds: List?)
+ fun purgeHistory()
+ fun updateLastAccess(tabId: String?, lastAccess: Long?)
+
+ companion object {
+ /** The codec used by GeckoSessionApi. */
+ val codec: MessageCodec by lazy {
+ GeckoPigeonCodec()
+ }
+ /** Sets up an instance of `GeckoSessionApi` to handle messages through the `binaryMessenger`. */
+ @JvmOverloads
+ fun setUp(binaryMessenger: BinaryMessenger, api: GeckoSessionApi?, messageChannelSuffix: String = "") {
+ val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.loadUrl$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val urlArg = args[1] as String
+ val flagsArg = args[2] as LoadUrlFlagsValue
+ val additionalHeadersArg = args[3] as Map?
+ val wrapped: List = try {
+ api.loadUrl(tabIdArg, urlArg, flagsArg, additionalHeadersArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.loadData$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val dataArg = args[1] as String
+ val mimeTypeArg = args[2] as String
+ val encodingArg = args[3] as String
+ val wrapped: List = try {
+ api.loadData(tabIdArg, dataArg, mimeTypeArg, encodingArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.reload$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val flagsArg = args[1] as LoadUrlFlagsValue
+ val wrapped: List = try {
+ api.reload(tabIdArg, flagsArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.stopLoading$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val wrapped: List = try {
+ api.stopLoading(tabIdArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.goBack$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val userInteractionArg = args[1] as Boolean
+ val wrapped: List = try {
+ api.goBack(tabIdArg, userInteractionArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.goForward$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val userInteractionArg = args[1] as Boolean
+ val wrapped: List = try {
+ api.goForward(tabIdArg, userInteractionArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.goToHistoryIndex$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val indexArg = args[0] as Long
+ val tabIdArg = args[1] as String?
+ val wrapped: List = try {
+ api.goToHistoryIndex(indexArg, tabIdArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.requestDesktopSite$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val enableArg = args[1] as Boolean
+ val wrapped: List = try {
+ api.requestDesktopSite(tabIdArg, enableArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.exitFullscreen$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val wrapped: List = try {
+ api.exitFullscreen(tabIdArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.saveToPdf$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val wrapped: List = try {
+ api.saveToPdf(tabIdArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.printContent$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val wrapped: List = try {
+ api.printContent(tabIdArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.translate$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val fromLanguageArg = args[1] as String
+ val toLanguageArg = args[2] as String
+ val optionsArg = args[3] as TranslationOptions?
+ val wrapped: List = try {
+ api.translate(tabIdArg, fromLanguageArg, toLanguageArg, optionsArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.translateRestore$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val wrapped: List = try {
+ api.translateRestore(tabIdArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.crashRecovery$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdsArg = args[0] as List?
+ val wrapped: List = try {
+ api.crashRecovery(tabIdsArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.purgeHistory$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { _, reply ->
+ val wrapped: List = try {
+ api.purgeHistory()
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.updateLastAccess$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String?
+ val lastAccessArg = args[1] as Long?
+ val wrapped: List = try {
+ api.updateLastAccess(tabIdArg, lastAccessArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ }
+ }
+}
+/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
+interface GeckoTabsApi {
+ fun selectTab(tabId: String)
+ fun removeTab(tabId: String)
+ fun addTab(url: String, selectTab: Boolean, startLoading: Boolean, parentId: String?, flags: LoadUrlFlagsValue, contextId: String?, source: SourceValue, private: Boolean, historyMetadata: HistoryMetadataKey?, additionalHeaders: Map?): String
+ fun removeAllTabs(recoverable: Boolean)
+ fun removeTabs(ids: List)
+ fun removeNormalTabs()
+ fun removePrivateTabs()
+ fun undo()
+ fun restoreTabsByList(tabs: List, selectTabId: String?, restoreLocation: RestoreLocation)
+ fun restoreTabsByBrowserState(state: RecoverableBrowserState, restoreLocation: RestoreLocation)
+ /**
+ * Selects an already existing tab with the matching [HistoryMetadataKey] or otherwise
+ * creates a new tab with the given [url].
+ */
+ fun selectOrAddTabByHistory(url: String, historyMetadata: HistoryMetadataKey): String
+ /** Selects an already existing tab displaying [url] or otherwise creates a new tab. */
+ fun selectOrAddTabByUrl(url: String, private: Boolean, source: SourceValue, flags: LoadUrlFlagsValue, ignoreFragment: Boolean): String
+ fun duplicateTab(selectTabId: String?, selectNewTab: Boolean): String
+ fun moveTabs(tabIds: List, targetTabId: String, placeAfter: Boolean)
+ fun migratePrivateTabUseCase(tabId: String, alternativeUrl: String?): String
+
+ companion object {
+ /** The codec used by GeckoTabsApi. */
+ val codec: MessageCodec by lazy {
+ GeckoPigeonCodec()
+ }
+ /** Sets up an instance of `GeckoTabsApi` to handle messages through the `binaryMessenger`. */
+ @JvmOverloads
+ fun setUp(binaryMessenger: BinaryMessenger, api: GeckoTabsApi?, messageChannelSuffix: String = "") {
+ val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.selectTab$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String
+ val wrapped: List = try {
+ api.selectTab(tabIdArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.removeTab$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String
+ val wrapped: List = try {
+ api.removeTab(tabIdArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.addTab$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val urlArg = args[0] as String
+ val selectTabArg = args[1] as Boolean
+ val startLoadingArg = args[2] as Boolean
+ val parentIdArg = args[3] as String?
+ val flagsArg = args[4] as LoadUrlFlagsValue
+ val contextIdArg = args[5] as String?
+ val sourceArg = args[6] as SourceValue
+ val privateArg = args[7] as Boolean
+ val historyMetadataArg = args[8] as HistoryMetadataKey?
+ val additionalHeadersArg = args[9] as Map?
+ val wrapped: List = try {
+ listOf(api.addTab(urlArg, selectTabArg, startLoadingArg, parentIdArg, flagsArg, contextIdArg, sourceArg, privateArg, historyMetadataArg, additionalHeadersArg))
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.removeAllTabs$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val recoverableArg = args[0] as Boolean
+ val wrapped: List = try {
+ api.removeAllTabs(recoverableArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.removeTabs$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val idsArg = args[0] as List
+ val wrapped: List = try {
+ api.removeTabs(idsArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.removeNormalTabs$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { _, reply ->
+ val wrapped: List = try {
+ api.removeNormalTabs()
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.removePrivateTabs$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { _, reply ->
+ val wrapped: List = try {
+ api.removePrivateTabs()
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.undo$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { _, reply ->
+ val wrapped: List = try {
+ api.undo()
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.restoreTabsByList$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabsArg = args[0] as List
+ val selectTabIdArg = args[1] as String?
+ val restoreLocationArg = args[2] as RestoreLocation
+ val wrapped: List = try {
+ api.restoreTabsByList(tabsArg, selectTabIdArg, restoreLocationArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.restoreTabsByBrowserState$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val stateArg = args[0] as RecoverableBrowserState
+ val restoreLocationArg = args[1] as RestoreLocation
+ val wrapped: List = try {
+ api.restoreTabsByBrowserState(stateArg, restoreLocationArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.selectOrAddTabByHistory$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val urlArg = args[0] as String
+ val historyMetadataArg = args[1] as HistoryMetadataKey
+ val wrapped: List = try {
+ listOf(api.selectOrAddTabByHistory(urlArg, historyMetadataArg))
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.selectOrAddTabByUrl$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val urlArg = args[0] as String
+ val privateArg = args[1] as Boolean
+ val sourceArg = args[2] as SourceValue
+ val flagsArg = args[3] as LoadUrlFlagsValue
+ val ignoreFragmentArg = args[4] as Boolean
+ val wrapped: List = try {
+ listOf(api.selectOrAddTabByUrl(urlArg, privateArg, sourceArg, flagsArg, ignoreFragmentArg))
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.duplicateTab$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val selectTabIdArg = args[0] as String?
+ val selectNewTabArg = args[1] as Boolean
+ val wrapped: List = try {
+ listOf(api.duplicateTab(selectTabIdArg, selectNewTabArg))
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.moveTabs$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdsArg = args[0] as List
+ val targetTabIdArg = args[1] as String
+ val placeAfterArg = args[2] as Boolean
+ val wrapped: List = try {
+ api.moveTabs(tabIdsArg, targetTabIdArg, placeAfterArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.migratePrivateTabUseCase$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val tabIdArg = args[0] as String
+ val alternativeUrlArg = args[1] as String?
+ val wrapped: List = try {
+ listOf(api.migratePrivateTabUseCase(tabIdArg, alternativeUrlArg))
+ } catch (exception: Throwable) {
+ wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/flutter_mozilla_components/android/src/main/res/drawable/ic_launcher_foreground.xml b/packages/flutter_mozilla_components/android/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 00000000..da5281c8
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/packages/flutter_mozilla_components/android/src/main/res/layout/fragment_browser.xml b/packages/flutter_mozilla_components/android/src/main/res/layout/fragment_browser.xml
new file mode 100644
index 00000000..05bd34cb
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/res/layout/fragment_browser.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/flutter_mozilla_components/android/src/main/res/values/styles.xml b/packages/flutter_mozilla_components/android/src/main/res/values/styles.xml
new file mode 100644
index 00000000..ed88a36e
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/main/res/values/styles.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/packages/flutter_mozilla_components/android/src/test/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPluginTest.kt b/packages/flutter_mozilla_components/android/src/test/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPluginTest.kt
new file mode 100644
index 00000000..2c91e256
--- /dev/null
+++ b/packages/flutter_mozilla_components/android/src/test/kotlin/eu/lensai/flutter_mozilla_components/FlutterMozillaComponentsPluginTest.kt
@@ -0,0 +1,27 @@
+package eu.lensai.flutter_mozilla_components
+
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import kotlin.test.Test
+import org.mockito.Mockito
+
+/*
+ * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
+ *
+ * Once you have built the plugin's example app, you can run these tests from the command
+ * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
+ * you can run them directly from IDEs that support JUnit such as Android Studio.
+ */
+
+internal class FlutterMozillaComponentsPluginTest {
+ @Test
+ fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
+ val plugin = FlutterMozillaComponentsPlugin()
+
+ val call = MethodCall("getPlatformVersion", null)
+ val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
+ plugin.onMethodCall(call, mockResult)
+
+ Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
+ }
+}
diff --git a/packages/flutter_mozilla_components/example/.gitignore b/packages/flutter_mozilla_components/example/.gitignore
new file mode 100644
index 00000000..79c113f9
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/.gitignore
@@ -0,0 +1,45 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/packages/flutter_mozilla_components/example/README.md b/packages/flutter_mozilla_components/example/README.md
new file mode 100644
index 00000000..b46510d7
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/README.md
@@ -0,0 +1,16 @@
+# flutter_mozilla_components_example
+
+Demonstrates how to use the flutter_mozilla_components plugin.
+
+## Getting Started
+
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
+- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
+
+For help getting started with Flutter development, view the
+[online documentation](https://docs.flutter.dev/), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
diff --git a/packages/flutter_mozilla_components/example/analysis_options.yaml b/packages/flutter_mozilla_components/example/analysis_options.yaml
new file mode 100644
index 00000000..0d290213
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/analysis_options.yaml
@@ -0,0 +1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/packages/flutter_mozilla_components/example/android/.gitignore b/packages/flutter_mozilla_components/example/android/.gitignore
new file mode 100644
index 00000000..55afd919
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/to/reference-keystore
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/packages/flutter_mozilla_components/example/android/app/build.gradle b/packages/flutter_mozilla_components/example/android/app/build.gradle
new file mode 100644
index 00000000..65b583dc
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/app/build.gradle
@@ -0,0 +1,44 @@
+plugins {
+ id "com.android.application"
+ id "kotlin-android"
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id "dev.flutter.flutter-gradle-plugin"
+}
+
+android {
+ namespace = "eu.lensai.flutter_mozilla_components_example"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_1_8
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "eu.lensai.flutter_mozilla_components_example"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/packages/flutter_mozilla_components/example/android/app/src/debug/AndroidManifest.xml b/packages/flutter_mozilla_components/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 00000000..399f6981
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/packages/flutter_mozilla_components/example/android/app/src/main/AndroidManifest.xml b/packages/flutter_mozilla_components/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5ecd30e
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/flutter_mozilla_components/example/android/app/src/main/kotlin/eu/lensai/flutter_mozilla_components_example/MainActivity.kt b/packages/flutter_mozilla_components/example/android/app/src/main/kotlin/eu/lensai/flutter_mozilla_components_example/MainActivity.kt
new file mode 100644
index 00000000..16bf8d66
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/app/src/main/kotlin/eu/lensai/flutter_mozilla_components_example/MainActivity.kt
@@ -0,0 +1,5 @@
+package eu.lensai.flutter_mozilla_components_example
+
+import io.flutter.embedding.android.FlutterFragmentActivity
+
+class MainActivity: FlutterFragmentActivity()
diff --git a/packages/flutter_mozilla_components/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/flutter_mozilla_components/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 00000000..f74085f3
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/packages/flutter_mozilla_components/example/android/app/src/main/res/drawable/launch_background.xml b/packages/flutter_mozilla_components/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 00000000..304732f8
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..db77bb4b
Binary files /dev/null and b/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..17987b79
Binary files /dev/null and b/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..09d43914
Binary files /dev/null and b/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..d5f1c8d3
Binary files /dev/null and b/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..4d6372ee
Binary files /dev/null and b/packages/flutter_mozilla_components/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/packages/flutter_mozilla_components/example/android/app/src/main/res/values-night/styles.xml b/packages/flutter_mozilla_components/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 00000000..573347a1
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/packages/flutter_mozilla_components/example/android/app/src/main/res/values/styles.xml b/packages/flutter_mozilla_components/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..e0759f9e
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/packages/flutter_mozilla_components/example/android/app/src/profile/AndroidManifest.xml b/packages/flutter_mozilla_components/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 00000000..399f6981
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/packages/flutter_mozilla_components/example/android/build.gradle b/packages/flutter_mozilla_components/example/android/build.gradle
new file mode 100644
index 00000000..d2ffbffa
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/build.gradle
@@ -0,0 +1,18 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.buildDir = "../build"
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean", Delete) {
+ delete rootProject.buildDir
+}
diff --git a/packages/flutter_mozilla_components/example/android/gradle.properties b/packages/flutter_mozilla_components/example/android/gradle.properties
new file mode 100644
index 00000000..25971708
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/packages/flutter_mozilla_components/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/flutter_mozilla_components/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..5d6560a4
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
diff --git a/packages/flutter_mozilla_components/example/android/settings.gradle b/packages/flutter_mozilla_components/example/android/settings.gradle
new file mode 100644
index 00000000..e6da251d
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/android/settings.gradle
@@ -0,0 +1,25 @@
+pluginManagement {
+ def flutterSdkPath = {
+ def properties = new Properties()
+ file("local.properties").withInputStream { properties.load(it) }
+ def flutterSdkPath = properties.getProperty("flutter.sdk")
+ assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+ return flutterSdkPath
+ }()
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id "dev.flutter.flutter-plugin-loader" version "1.0.0"
+ id "com.android.application" version "8.1.0" apply false
+ id "org.jetbrains.kotlin.android" version "1.9.20" apply false
+}
+
+include ":app"
diff --git a/packages/flutter_mozilla_components/example/lib/main.dart b/packages/flutter_mozilla_components/example/lib/main.dart
new file mode 100644
index 00000000..d18dcbe2
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/lib/main.dart
@@ -0,0 +1,53 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
+
+void main() {
+ runApp(const MyApp());
+}
+
+class MyApp extends StatefulWidget {
+ const MyApp({super.key});
+
+ @override
+ MyAppState createState() => MyAppState();
+}
+
+class MyAppState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ home: Scaffold(
+ appBar: AppBar(
+ title: const Text('Native Fragment Example'),
+ actions: [
+ MenuAnchor(
+ builder: (context, controller, child) => IconButton(
+ onPressed: () {
+ controller.open();
+ },
+ icon: const Icon(Icons.menu)),
+ menuChildren: [
+ MenuItemButton(
+ onPressed: () async {
+ await GeckoSessionService.forCurrentTab().reload();
+ },
+ child: const Text('Reload'),
+ ),
+ MenuItemButton(
+ onPressed: () async {
+ await GeckoSessionService.forCurrentTab().goBack();
+ },
+ child: const Text('Back'),
+ )
+ ])
+ ],
+ ),
+ body: const SafeArea(
+ child: Center(
+ child: GeckoView(),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/packages/flutter_mozilla_components/example/pubspec.yaml b/packages/flutter_mozilla_components/example/pubspec.yaml
new file mode 100644
index 00000000..933e6de0
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/pubspec.yaml
@@ -0,0 +1,85 @@
+name: flutter_mozilla_components_example
+description: "Demonstrates how to use the flutter_mozilla_components plugin."
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+environment:
+ sdk: ^3.6.0-198.0.dev
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+ flutter:
+ sdk: flutter
+
+ flutter_mozilla_components:
+ # When depending on this package from a real application you should use:
+ # flutter_mozilla_components: ^x.y.z
+ # See https://dart.dev/tools/pub/dependencies#version-constraints
+ # The example app is bundled with the plugin so we use a path dependency on
+ # the parent directory to use the current plugin's version.
+ path: ../
+
+ # The following adds the Cupertino Icons font to your application.
+ # Use with the CupertinoIcons class for iOS style icons.
+ cupertino_icons: ^1.0.8
+
+dev_dependencies:
+ integration_test:
+ sdk: flutter
+ flutter_test:
+ sdk: flutter
+
+ # The "flutter_lints" package below contains a set of recommended lints to
+ # encourage good coding practices. The lint set provided by the package is
+ # activated in the `analysis_options.yaml` file located at the root of your
+ # package. See that file for information about deactivating specific lint
+ # rules and activating additional ones.
+ flutter_lints: ^4.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+
+ # The following line ensures that the Material Icons font is
+ # included with your application, so that you can use the icons in
+ # the material Icons class.
+ uses-material-design: true
+
+ # To add assets to your application, add an assets section, like this:
+ # assets:
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/to/resolution-aware-images
+
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.dev/to/asset-from-package
+
+ # To add custom fonts to your application, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.dev/to/font-from-package
diff --git a/packages/flutter_mozilla_components/example/pubspec_overrides.yaml b/packages/flutter_mozilla_components/example/pubspec_overrides.yaml
new file mode 100644
index 00000000..cd7ef4bd
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/pubspec_overrides.yaml
@@ -0,0 +1,4 @@
+# melos_managed_dependency_overrides: flutter_mozilla_components
+dependency_overrides:
+ flutter_mozilla_components:
+ path: ..
diff --git a/packages/flutter_mozilla_components/example/test/widget_test.dart b/packages/flutter_mozilla_components/example/test/widget_test.dart
new file mode 100644
index 00000000..89533605
--- /dev/null
+++ b/packages/flutter_mozilla_components/example/test/widget_test.dart
@@ -0,0 +1,27 @@
+// This is a basic Flutter widget test.
+//
+// To perform an interaction with a widget in your test, use the WidgetTester
+// utility in the flutter_test package. For example, you can send tap and scroll
+// gestures. You can also use WidgetTester to find child widgets in the widget
+// tree, read text, and verify that the values of widget properties are correct.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:flutter_mozilla_components_example/main.dart';
+
+void main() {
+ testWidgets('Verify Platform version', (WidgetTester tester) async {
+ // Build our app and trigger a frame.
+ await tester.pumpWidget(const MyApp());
+
+ // Verify that platform version is retrieved.
+ expect(
+ find.byWidgetPredicate(
+ (Widget widget) => widget is Text &&
+ widget.data!.startsWith('Running on:'),
+ ),
+ findsOneWidget,
+ );
+ });
+}
diff --git a/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart b/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart
new file mode 100644
index 00000000..441dfae6
--- /dev/null
+++ b/packages/flutter_mozilla_components/lib/flutter_mozilla_components.dart
@@ -0,0 +1,4 @@
+export 'src/geckoview_widget.dart';
+//export 'src/domain/services/gecko_browser.dart';
+export 'src/domain/services/gecko_session.dart';
+export 'src/domain/services/gecko_tabs.dart';
diff --git a/packages/flutter_mozilla_components/lib/src/data/models/load_url_flags.dart b/packages/flutter_mozilla_components/lib/src/data/models/load_url_flags.dart
new file mode 100644
index 00000000..ced2af11
--- /dev/null
+++ b/packages/flutter_mozilla_components/lib/src/data/models/load_url_flags.dart
@@ -0,0 +1,46 @@
+// ignore_for_file: constant_identifier_names, non_constant_identifier_names
+
+import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
+
+/// Describes a combination of flags provided to the engine when loading a URL.
+class LoadUrlFlags {
+ final int value;
+
+ const LoadUrlFlags._(this.value);
+
+ LoadUrlFlagsValue toValue() {
+ return LoadUrlFlagsValue(value: value);
+ }
+
+ static const LoadUrlFlags NONE = LoadUrlFlags._(0);
+ static const LoadUrlFlags BYPASS_CACHE = LoadUrlFlags._(1 << 0);
+ static const LoadUrlFlags BYPASS_PROXY = LoadUrlFlags._(1 << 1);
+ static const LoadUrlFlags EXTERNAL = LoadUrlFlags._(1 << 2);
+ static const LoadUrlFlags ALLOW_POPUPS = LoadUrlFlags._(1 << 3);
+ static const LoadUrlFlags BYPASS_CLASSIFIER = LoadUrlFlags._(1 << 4);
+ static const LoadUrlFlags LOAD_FLAGS_FORCE_ALLOW_DATA_URI =
+ LoadUrlFlags._(1 << 5);
+ static const LoadUrlFlags LOAD_FLAGS_REPLACE_HISTORY = LoadUrlFlags._(1 << 6);
+ static const LoadUrlFlags LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE =
+ LoadUrlFlags._(1 << 7);
+ static const LoadUrlFlags ALLOW_ADDITIONAL_HEADERS = LoadUrlFlags._(1 << 15);
+ static const LoadUrlFlags ALLOW_JAVASCRIPT_URL = LoadUrlFlags._(1 << 16);
+
+ static LoadUrlFlags ALL = LoadUrlFlags._(
+ BYPASS_CACHE.value |
+ BYPASS_PROXY.value |
+ EXTERNAL.value |
+ ALLOW_POPUPS.value |
+ BYPASS_CLASSIFIER.value |
+ LOAD_FLAGS_FORCE_ALLOW_DATA_URI.value |
+ LOAD_FLAGS_REPLACE_HISTORY.value |
+ LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE.value |
+ ALLOW_ADDITIONAL_HEADERS.value |
+ ALLOW_JAVASCRIPT_URL.value,
+ );
+
+ static LoadUrlFlags select(List flags) {
+ int combinedValue = flags.fold(0, (sum, flag) => sum | flag.value);
+ return LoadUrlFlags._(combinedValue);
+ }
+}
diff --git a/packages/flutter_mozilla_components/lib/src/data/models/source.dart b/packages/flutter_mozilla_components/lib/src/data/models/source.dart
new file mode 100644
index 00000000..bfff01c9
--- /dev/null
+++ b/packages/flutter_mozilla_components/lib/src/data/models/source.dart
@@ -0,0 +1,168 @@
+import 'package:flutter_mozilla_components/src/helper/helper.dart';
+import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
+
+/// Describes a category of an external package.
+enum PackageCategory {
+ unknown(-1),
+ game(0),
+ audio(1),
+ video(2),
+ image(3),
+ social(4),
+ news(5),
+ maps(6),
+ productivity(7);
+
+ final int id;
+ const PackageCategory(this.id);
+
+ PackageCategoryValue toValue() {
+ return PackageCategoryValue(value: id);
+ }
+}
+
+/// Represents the origin of a session to describe how and why it was created.
+abstract class Source {
+ final int id;
+
+ const Source(this.id);
+
+ SourceValue toValue();
+
+ /// Initializes a [Source] of a correct type from its component properties.
+ /// Intended use is for restoring persisted state.
+ static Source restore(
+ int? sourceId, String? packageId, int? packageCategory) {
+ final caller = packageId != null
+ ? ExternalPackage(
+ packageId: packageId,
+ category: packageCategoryfromInt(packageCategory).toValue())
+ : null;
+
+ switch (sourceId) {
+ case 1:
+ return External.actionSend(caller);
+ case 2:
+ return External.actionView(caller);
+ case 3:
+ return External.actionSearch(caller);
+ case 4:
+ return External.customTab(caller);
+ case 5:
+ return Internal.homeScreen;
+ case 6:
+ return Internal.menu;
+ case 7:
+ return Internal.newTab;
+ case 8:
+ return Internal.none;
+ case 9:
+ return Internal.textSelection;
+ case 10:
+ return Internal.userEntered;
+ case 11:
+ return Internal.customTab;
+ default:
+ return Internal.none;
+ }
+ }
+}
+
+/// Describes sessions of external origins, i.e. from outside of the application.
+abstract class External extends Source {
+ final ExternalPackage? caller;
+
+ const External(super.id, this.caller);
+
+ @override
+ SourceValue toValue() {
+ return SourceValue(id: id, caller: caller);
+ }
+
+ /// Created to handle an ACTION_SEND (share) intent.
+ factory External.actionSend(ExternalPackage? caller) = ActionSend;
+
+ /// Created to handle an ACTION_VIEW intent.
+ factory External.actionView(ExternalPackage? caller) = ActionView;
+
+ /// Created to handle an ACTION_SEARCH and ACTION_WEB_SEARCH intent.
+ factory External.actionSearch(ExternalPackage? caller) = ActionSearch;
+
+ /// Created to handle a CustomTabs intent of external origin.
+ factory External.customTab(ExternalPackage? caller) = CustomTab;
+}
+
+class ActionSend extends External {
+ const ActionSend(ExternalPackage? caller) : super(1, caller);
+}
+
+class ActionView extends External {
+ const ActionView(ExternalPackage? caller) : super(2, caller);
+}
+
+class ActionSearch extends External {
+ const ActionSearch(ExternalPackage? caller) : super(3, caller);
+}
+
+class CustomTab extends External {
+ const CustomTab(ExternalPackage? caller) : super(4, caller);
+}
+
+/// Describes sessions of internal origin, i.e. from within of the application.
+abstract class Internal extends Source {
+ const Internal(super.id);
+
+ @override
+ SourceValue toValue() {
+ return SourceValue(id: id);
+ }
+
+ /// User interacted with the home screen.
+ static const homeScreen = _HomeScreen();
+
+ /// User interacted with a menu.
+ static const menu = _Menu();
+
+ /// User opened a new tab.
+ static const newTab = _NewTab();
+
+ /// Default value and for testing purposes.
+ static const none = _None();
+
+ /// User selected text.
+ static const textSelection = _TextSelection();
+
+ /// User entered a URL or search term.
+ static const userEntered = _UserEntered();
+
+ /// Created to handle a CustomTabs intent of internal origin.
+ static const customTab = _CustomTab();
+}
+
+class _HomeScreen extends Internal {
+ const _HomeScreen() : super(5);
+}
+
+class _Menu extends Internal {
+ const _Menu() : super(6);
+}
+
+class _NewTab extends Internal {
+ const _NewTab() : super(7);
+}
+
+class _None extends Internal {
+ const _None() : super(8);
+}
+
+class _TextSelection extends Internal {
+ const _TextSelection() : super(9);
+}
+
+class _UserEntered extends Internal {
+ const _UserEntered() : super(10);
+}
+
+class _CustomTab extends Internal {
+ const _CustomTab() : super(11);
+}
diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_browser.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_browser.dart
new file mode 100644
index 00000000..c3a8dc34
--- /dev/null
+++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_browser.dart
@@ -0,0 +1,11 @@
+import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
+
+class GeckoBrowserService {
+ final GeckoBrowserApi _api;
+
+ GeckoBrowserService({GeckoBrowserApi? api}) : _api = api ?? GeckoBrowserApi();
+
+ Future showNativeFragment() {
+ return _api.showNativeFragment();
+ }
+}
diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_session.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_session.dart
new file mode 100644
index 00000000..f3a64e3e
--- /dev/null
+++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_session.dart
@@ -0,0 +1,128 @@
+import 'package:flutter_mozilla_components/src/data/models/load_url_flags.dart';
+import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
+
+class GeckoSessionService {
+ final String? tabId;
+ final GeckoSessionApi _api;
+
+ GeckoSessionService.forCurrentTab({GeckoSessionApi? api})
+ : _api = api ?? GeckoSessionApi(),
+ tabId = null;
+
+ GeckoSessionService({required String this.tabId, GeckoSessionApi? api})
+ : _api = api ?? GeckoSessionApi();
+
+ Future loadUrl({
+ required Uri url,
+ LoadUrlFlags flags = LoadUrlFlags.NONE,
+ Map? additionalHeaders,
+ }) {
+ return _api.loadUrl(
+ tabId: tabId,
+ url: url.toString(),
+ flags: flags.toValue(),
+ additionalHeaders: additionalHeaders,
+ );
+ }
+
+ Future loadData({
+ required String data,
+ required String mimeType,
+ String encoding = "UTF-8",
+ }) {
+ return _api.loadData(
+ tabId: tabId,
+ data: data,
+ mimeType: mimeType,
+ encoding: encoding,
+ );
+ }
+
+ Future reload({LoadUrlFlags flags = LoadUrlFlags.NONE}) {
+ return _api.reload(
+ tabId: tabId,
+ flags: flags.toValue(),
+ );
+ }
+
+ Future stopLoading() {
+ return _api.stopLoading(tabId: tabId);
+ }
+
+ Future goBack({bool userInteraction = true}) {
+ return _api.goBack(
+ tabId: tabId,
+ userInteraction: userInteraction,
+ );
+ }
+
+ Future goForward({bool userInteraction = true}) {
+ return _api.goForward(
+ tabId: tabId,
+ userInteraction: userInteraction,
+ );
+ }
+
+ Future goToHistoryIndex({required int index}) {
+ return _api.goToHistoryIndex(
+ index: index,
+ tabId: tabId,
+ );
+ }
+
+ Future requestDesktopSite({required bool enable}) {
+ return _api.requestDesktopSite(
+ enable: enable,
+ tabId: tabId,
+ );
+ }
+
+ Future exitFullscreen() {
+ return _api.exitFullscreen(tabId: tabId);
+ }
+
+ Future saveToPdf() {
+ return _api.saveToPdf(tabId: tabId);
+ }
+
+ Future printContent() {
+ return _api.printContent(tabId: tabId);
+ }
+
+ Future translate({
+ required String fromLanguage,
+ required String toLanguage,
+ TranslationOptions? options,
+ }) {
+ return _api.translate(
+ tabId: tabId,
+ fromLanguage: fromLanguage,
+ toLanguage: toLanguage,
+ options: options,
+ );
+ }
+
+ Future translateRestore() {
+ return _api.translateRestore(tabId: tabId);
+ }
+
+ Future crashRecovery({
+ List? tabIds,
+ }) {
+ return _api.crashRecovery(tabIds: tabIds);
+ }
+
+ Future purgeHistory() {
+ return _api.purgeHistory();
+ }
+
+ Future updateLastAccess({
+ String? tabId, //If null = current tab
+ DateTime? lastAccess, //If null datetime.now
+ }) {
+ return _api.updateLastAccess(
+ tabId: tabId,
+ lastAccess: (lastAccess ?? DateTime.now()).millisecondsSinceEpoch,
+ );
+ }
+}
diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_tabs.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_tabs.dart
new file mode 100644
index 00000000..5451467c
--- /dev/null
+++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_tabs.dart
@@ -0,0 +1,135 @@
+import 'package:flutter_mozilla_components/src/data/models/load_url_flags.dart';
+import 'package:flutter_mozilla_components/src/data/models/source.dart';
+import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
+
+class GeckoTabsService {
+ final GeckoTabsApi _api;
+
+ GeckoTabsService({GeckoTabsApi? api}) : _api = api ?? GeckoTabsApi();
+
+ Future selectTab({required String tabId}) {
+ return _api.selectTab(tabId: tabId);
+ }
+
+ Future removeTab({required String tabId}) {
+ return _api.removeTab(tabId: tabId);
+ }
+
+ Future addTab({
+ Uri? url,
+ bool selectTab = true,
+ bool startLoading = true,
+ String? parentId,
+ LoadUrlFlags flags = LoadUrlFlags.NONE,
+ String? contextId,
+ Source source = Internal.newTab,
+ bool private = false,
+ HistoryMetadataKey? historyMetadata,
+ Map? additionalHeaders,
+ }) {
+ return _api.addTab(
+ url: (url ?? Uri.parse('about:blank')).toString(),
+ selectTab: selectTab,
+ startLoading: startLoading,
+ parentId: parentId,
+ flags: flags.toValue(),
+ contextId: contextId,
+ source: source.toValue(),
+ private: private,
+ historyMetadata: historyMetadata,
+ additionalHeaders: additionalHeaders,
+ );
+ }
+
+ Future removeAllTabs({required bool recoverable}) {
+ return _api.removeAllTabs(recoverable: recoverable);
+ }
+
+ Future removeTabs({required List ids}) {
+ return _api.removeTabs(ids: ids);
+ }
+
+ Future removeNormalTabs() {
+ return _api.removeNormalTabs();
+ }
+
+ Future removePrivateTabs() {
+ return _api.removePrivateTabs();
+ }
+
+ Future undo() {
+ return _api.undo();
+ }
+
+ //restoreTabs invokes splitted
+ Future restoreTabsByList({
+ required List tabs,
+ String? selectTabId,
+ RestoreLocation restoreLocation = RestoreLocation.end,
+ }) {
+ return _api.restoreTabsByList(
+ tabs: tabs, selectTabId: selectTabId, restoreLocation: restoreLocation);
+ }
+
+ Future restoreTabsByBrowserState({
+ required RecoverableBrowserState state,
+ RestoreLocation restoreLocation = RestoreLocation.end,
+ }) {
+ return _api.restoreTabsByBrowserState(
+ state: state, restoreLocation: restoreLocation);
+ }
+ //The calls with engin storage for restore are not supported at the moment
+
+ //selectOrAddTab invokes splitted
+ /// Selects an already existing tab with the matching [HistoryMetadataKey] or otherwise
+ /// creates a new tab with the given [url].
+ Future selectOrAddTabByHistory({
+ required Uri url,
+ required HistoryMetadataKey historyMetadata,
+ }) {
+ return _api.selectOrAddTabByHistory(
+ url: url.toString(), historyMetadata: historyMetadata);
+ }
+
+ /// Selects an already existing tab displaying [url] or otherwise creates a new tab.
+ Future selectOrAddTabByUrl({
+ required Uri url,
+ bool private = false,
+ Source source = Internal.newTab,
+ LoadUrlFlags flags = LoadUrlFlags.NONE,
+ bool ignoreFragment = false,
+ }) {
+ return _api.selectOrAddTabByUrl(
+ url: url.toString(),
+ private: private,
+ source: source.toValue(),
+ flags: flags.toValue(),
+ ignoreFragment: ignoreFragment,
+ );
+ }
+
+ Future duplicateTab({
+ String? selectTabId,
+ bool selectNewTab = true,
+ }) {
+ return _api.duplicateTab(
+ selectTabId: selectTabId, selectNewTab: selectNewTab);
+ }
+
+ Future moveTabs({
+ required List tabIds,
+ required String targetTabId,
+ required bool placeAfter,
+ }) {
+ return _api.moveTabs(
+ tabIds: tabIds, targetTabId: targetTabId, placeAfter: placeAfter);
+ }
+
+ Future migratePrivateTabUseCase({
+ required String tabId,
+ Uri? alternativeUrl,
+ }) {
+ return _api.migratePrivateTabUseCase(
+ tabId: tabId, alternativeUrl: alternativeUrl?.toString());
+ }
+}
diff --git a/packages/flutter_mozilla_components/lib/src/geckoview_widget.dart b/packages/flutter_mozilla_components/lib/src/geckoview_widget.dart
new file mode 100644
index 00000000..9f6f76cd
--- /dev/null
+++ b/packages/flutter_mozilla_components/lib/src/geckoview_widget.dart
@@ -0,0 +1,45 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_mozilla_components/src/domain/services/gecko_browser.dart';
+
+class GeckoView extends StatelessWidget {
+ const GeckoView({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return PlatformViewLink(
+ viewType: 'eu.lensai/gecko',
+ surfaceFactory: (
+ context,
+ controller,
+ ) {
+ return AndroidViewSurface(
+ controller: controller as AndroidViewController,
+ gestureRecognizers: const >{},
+ hitTestBehavior: PlatformViewHitTestBehavior.opaque,
+ );
+ },
+ onCreatePlatformView: (PlatformViewCreationParams params) {
+ return PlatformViewsService.initExpensiveAndroidView(
+ id: params.id,
+ viewType: 'eu.lensai/gecko',
+ layoutDirection: TextDirection.ltr,
+ creationParams: {},
+ creationParamsCodec: const StandardMessageCodec(),
+ )
+ ..addOnPlatformViewCreatedListener((value) async {
+ params.onPlatformViewCreated(value);
+
+ SchedulerBinding.instance.addPostFrameCallback((_) async {
+ await GeckoBrowserService().showNativeFragment();
+ });
+ })
+ ..create();
+ },
+ );
+ }
+}
diff --git a/packages/flutter_mozilla_components/lib/src/helper/helper.dart b/packages/flutter_mozilla_components/lib/src/helper/helper.dart
new file mode 100644
index 00000000..10d6398e
--- /dev/null
+++ b/packages/flutter_mozilla_components/lib/src/helper/helper.dart
@@ -0,0 +1,9 @@
+import 'package:flutter_mozilla_components/src/data/models/source.dart';
+
+/// Maps an int category (as it can be obtained from a package manager) to our internal representation.
+PackageCategory packageCategoryfromInt(int? id) {
+ return PackageCategory.values.firstWhere(
+ (category) => category.id == id,
+ orElse: () => PackageCategory.unknown,
+ );
+}
diff --git a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart
new file mode 100644
index 00000000..acd5a45e
--- /dev/null
+++ b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart
@@ -0,0 +1,1324 @@
+// Autogenerated from Pigeon (v22.3.0), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
+
+import 'dart:async';
+import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
+
+import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
+import 'package:flutter/services.dart';
+
+PlatformException _createConnectionError(String channelName) {
+ return PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel: "$channelName".',
+ );
+}
+
+/// Indicates what location the tabs should be restored at
+enum RestoreLocation {
+ /// Restore tabs at the beginning of the tab list
+ beginning,
+ /// Restore tabs at the end of the tab list
+ end,
+ /// Restore tabs at a specific index in the tab list
+ atIndex,
+}
+
+/// Translation options that map to the Gecko Translations Options.
+///
+/// @property downloadModel If the necessary models should be downloaded on request. If false, then
+/// the translation will not complete and throw an exception if the models are not already available.
+class TranslationOptions {
+ TranslationOptions({
+ required this.downloadModel,
+ });
+
+ bool downloadModel;
+
+ Object encode() {
+ return