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 [ + downloadModel, + ]; + } + + static TranslationOptions decode(Object result) { + result as List; + return TranslationOptions( + downloadModel: result[0]! as bool, + ); + } +} + +/// Value type that represents the state of reader mode/view. +class ReaderState { + ReaderState({ + required this.readerable, + required this.active, + required this.checkRequired, + required this.connectRequired, + this.baseUrl, + this.activeUrl, + this.scrollY, + }); + + /// Whether or not the current page can be transformed to + /// be displayed in a reader view. + bool readerable; + + /// Whether or not reader view is active. + bool active; + + /// Whether or not a readerable check is required for the + /// current page. + bool checkRequired; + + /// Whether or not a new connection to the reader view + /// content script is required. + bool connectRequired; + + /// The base URL of the reader view extension page. + String? baseUrl; + + /// The URL of the page currently displayed in reader view. + String? activeUrl; + + /// The vertical scroll position of the page currently + /// displayed in reader view. + int? scrollY; + + Object encode() { + return [ + readerable, + active, + checkRequired, + connectRequired, + baseUrl, + activeUrl, + scrollY, + ]; + } + + static ReaderState decode(Object result) { + result as List; + return ReaderState( + readerable: result[0]! as bool, + active: result[1]! as bool, + checkRequired: result[2]! as bool, + connectRequired: result[3]! as bool, + baseUrl: result[4] as String?, + activeUrl: result[5] as String?, + scrollY: result[6] as int?, + ); + } +} + +/// Details about the last playing media in this tab. +class LastMediaAccessState { + LastMediaAccessState({ + required this.lastMediaUrl, + required this.lastMediaAccess, + required this.mediaSessionActive, + }); + + /// [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. + String lastMediaUrl; + + /// 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. + int lastMediaAccess; + + /// 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. + bool mediaSessionActive; + + Object encode() { + return [ + lastMediaUrl, + lastMediaAccess, + mediaSessionActive, + ]; + } + + static LastMediaAccessState decode(Object result) { + result as List; + return LastMediaAccessState( + lastMediaUrl: result[0]! as String, + lastMediaAccess: result[1]! as int, + mediaSessionActive: result[2]! as bool, + ); + } +} + +/// 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. +class HistoryMetadataKey { + HistoryMetadataKey({ + required this.url, + this.searchTerm, + this.referrerUrl, + }); + + /// A url of the page. + String url; + + /// An optional search term if this record was + /// created as part of a search by the user. + String? searchTerm; + + /// An optional url of the parent/referrer if + /// this record was created in response to a user opening + /// a page in a new tab. + String? referrerUrl; + + Object encode() { + return [ + url, + searchTerm, + referrerUrl, + ]; + } + + static HistoryMetadataKey decode(Object result) { + result as List; + return HistoryMetadataKey( + url: result[0]! as String, + searchTerm: result[1] as String?, + referrerUrl: result[2] as String?, + ); + } +} + +class PackageCategoryValue { + PackageCategoryValue({ + required this.value, + }); + + int value; + + Object encode() { + return [ + value, + ]; + } + + static PackageCategoryValue decode(Object result) { + result as List; + return PackageCategoryValue( + value: result[0]! as int, + ); + } +} + +/// Describes an external package. +class ExternalPackage { + ExternalPackage({ + required this.packageId, + required this.category, + }); + + /// An Android package id. + String packageId; + + /// A [PackageCategory] as defined by the application. + PackageCategoryValue category; + + Object encode() { + return [ + packageId, + category, + ]; + } + + static ExternalPackage decode(Object result) { + result as List; + return ExternalPackage( + packageId: result[0]! as String, + category: result[1]! as PackageCategoryValue, + ); + } +} + +class LoadUrlFlagsValue { + LoadUrlFlagsValue({ + required this.value, + }); + + int value; + + Object encode() { + return [ + value, + ]; + } + + static LoadUrlFlagsValue decode(Object result) { + result as List; + return LoadUrlFlagsValue( + value: result[0]! as int, + ); + } +} + +class SourceValue { + SourceValue({ + required this.id, + this.caller, + }); + + int id; + + ExternalPackage? caller; + + Object encode() { + return [ + id, + caller, + ]; + } + + static SourceValue decode(Object result) { + result as List; + return SourceValue( + id: result[0]! as int, + caller: result[1] as ExternalPackage?, + ); + } +} + +/// 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. +class TabState { + TabState({ + required this.id, + required this.url, + this.parentId, + required this.title, + required this.searchTerm, + this.contextId, + required this.readerState, + required this.lastAccess, + required this.createdAt, + required this.lastMediaAccessState, + required this.private, + this.historyMetadata, + required this.source, + required this.index, + required this.hasFormData, + }); + + /// Unique ID identifying this tab. + String id; + + /// The last URL of this tab. + String url; + + /// The unique ID of the parent tab if this tab was opened from another tab (e.g. via + /// the context menu). + String? parentId; + + /// The last title of this tab (or an empty String). + String title; + + /// The last used search terms, or an empty string if no + /// search was executed for this session. + String searchTerm; + + /// The context ID ("container") this tab used (or null). + String? contextId; + + /// The last [ReaderState] of the tab. + ReaderState readerState; + + /// The last time this tab was selected. + int lastAccess; + + /// Timestamp of the tab's creation. + int createdAt; + + /// Details about the last time was playing in this tab. + LastMediaAccessState lastMediaAccessState; + + /// If tab was private. + bool private; + + /// The last [HistoryMetadataKey] of the tab. + HistoryMetadataKey? historyMetadata; + + /// The last [Source] of the tab. + SourceValue source; + + /// The index the tab should be restored at. + int index; + + /// Whether the tab has form data. + bool hasFormData; + + Object encode() { + return [ + id, + url, + parentId, + title, + searchTerm, + contextId, + readerState, + lastAccess, + createdAt, + lastMediaAccessState, + private, + historyMetadata, + source, + index, + hasFormData, + ]; + } + + static TabState decode(Object result) { + result as List; + return TabState( + id: result[0]! as String, + url: result[1]! as String, + parentId: result[2] as String?, + title: result[3]! as String, + searchTerm: result[4]! as String, + contextId: result[5] as String?, + readerState: result[6]! as ReaderState, + lastAccess: result[7]! as int, + createdAt: result[8]! as int, + lastMediaAccessState: result[9]! as LastMediaAccessState, + private: result[10]! as bool, + historyMetadata: result[11] as HistoryMetadataKey?, + source: result[12]! as SourceValue, + index: result[13]! as int, + hasFormData: result[14]! as bool, + ); + } +} + +/// A recoverable version of [TabState]. +class RecoverableTab { + RecoverableTab({ + this.engineSessionStateJson, + required this.state, + }); + + /// The [EngineSessionState] needed for restoring the previous state of this tab. + String? engineSessionStateJson; + + /// A [TabState] instance containing basic tab state. + TabState state; + + Object encode() { + return [ + engineSessionStateJson, + state, + ]; + } + + static RecoverableTab decode(Object result) { + result as List; + return RecoverableTab( + engineSessionStateJson: result[0] as String?, + state: result[1]! as TabState, + ); + } +} + +/// A restored browser state, read from disk. +class RecoverableBrowserState { + RecoverableBrowserState({ + required this.tabs, + this.selectedTabId, + }); + + /// The list of restored tabs. + List tabs; + + /// The ID of the selected tab in [tabs]. Or `null` if no selection was restored. + String? selectedTabId; + + Object encode() { + return [ + tabs, + selectedTabId, + ]; + } + + static RecoverableBrowserState decode(Object result) { + result as List; + return RecoverableBrowserState( + tabs: (result[0] as List?)!.cast(), + selectedTabId: result[1] as String?, + ); + } +} + + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is RestoreLocation) { + buffer.putUint8(129); + writeValue(buffer, value.index); + } else if (value is TranslationOptions) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else if (value is ReaderState) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); + } else if (value is LastMediaAccessState) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); + } else if (value is HistoryMetadataKey) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); + } else if (value is PackageCategoryValue) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else if (value is ExternalPackage) { + buffer.putUint8(135); + writeValue(buffer, value.encode()); + } else if (value is LoadUrlFlagsValue) { + buffer.putUint8(136); + writeValue(buffer, value.encode()); + } else if (value is SourceValue) { + buffer.putUint8(137); + writeValue(buffer, value.encode()); + } else if (value is TabState) { + buffer.putUint8(138); + writeValue(buffer, value.encode()); + } else if (value is RecoverableTab) { + buffer.putUint8(139); + writeValue(buffer, value.encode()); + } else if (value is RecoverableBrowserState) { + buffer.putUint8(140); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + final int? value = readValue(buffer) as int?; + return value == null ? null : RestoreLocation.values[value]; + case 130: + return TranslationOptions.decode(readValue(buffer)!); + case 131: + return ReaderState.decode(readValue(buffer)!); + case 132: + return LastMediaAccessState.decode(readValue(buffer)!); + case 133: + return HistoryMetadataKey.decode(readValue(buffer)!); + case 134: + return PackageCategoryValue.decode(readValue(buffer)!); + case 135: + return ExternalPackage.decode(readValue(buffer)!); + case 136: + return LoadUrlFlagsValue.decode(readValue(buffer)!); + case 137: + return SourceValue.decode(readValue(buffer)!); + case 138: + return TabState.decode(readValue(buffer)!); + case 139: + return RecoverableTab.decode(readValue(buffer)!); + case 140: + return RecoverableBrowserState.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class GeckoBrowserApi { + /// Constructor for [GeckoBrowserApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + GeckoBrowserApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future showNativeFragment() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoBrowserApi.showNativeFragment$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } +} + +class GeckoSessionApi { + /// Constructor for [GeckoSessionApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + GeckoSessionApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future loadUrl({required String? tabId, required String url, required LoadUrlFlagsValue flags, required Map? additionalHeaders,}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.loadUrl$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId, url, flags, additionalHeaders]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future loadData({required String? tabId, required String data, required String mimeType, required String encoding,}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.loadData$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId, data, mimeType, encoding]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future reload({required String? tabId, required LoadUrlFlagsValue flags}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.reload$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId, flags]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future stopLoading({required String? tabId}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.stopLoading$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future goBack({required String? tabId, required bool userInteraction}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.goBack$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId, userInteraction]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future goForward({required String? tabId, required bool userInteraction}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.goForward$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId, userInteraction]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future goToHistoryIndex({required int index, required String? tabId}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.goToHistoryIndex$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([index, tabId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future requestDesktopSite({required String? tabId, required bool enable}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.requestDesktopSite$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId, enable]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future exitFullscreen({required String? tabId}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.exitFullscreen$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future saveToPdf({required String? tabId}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.saveToPdf$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future printContent({required String? tabId}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.printContent$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future translate({required String? tabId, required String fromLanguage, required String toLanguage, required TranslationOptions? options,}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.translate$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId, fromLanguage, toLanguage, options]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future translateRestore({required String? tabId}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.translateRestore$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future crashRecovery({required List? tabIds}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.crashRecovery$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabIds]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future purgeHistory() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.purgeHistory$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future updateLastAccess({required String? tabId, required int? lastAccess}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoSessionApi.updateLastAccess$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId, lastAccess]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } +} + +class GeckoTabsApi { + /// Constructor for [GeckoTabsApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + GeckoTabsApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future selectTab({required String tabId}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.selectTab$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future removeTab({required String tabId}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.removeTab$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future addTab({required String url, required bool selectTab, required bool startLoading, required String? parentId, required LoadUrlFlagsValue flags, required String? contextId, required SourceValue source, required bool private, required HistoryMetadataKey? historyMetadata, required Map? additionalHeaders,}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.addTab$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([url, selectTab, startLoading, parentId, flags, contextId, source, private, historyMetadata, additionalHeaders]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + Future removeAllTabs({required bool recoverable}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.removeAllTabs$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([recoverable]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future removeTabs({required List ids}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.removeTabs$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([ids]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future removeNormalTabs() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.removeNormalTabs$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future removePrivateTabs() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.removePrivateTabs$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future undo() async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.undo$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send(null) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future restoreTabsByList({required List tabs, required String? selectTabId, required RestoreLocation restoreLocation,}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.restoreTabsByList$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabs, selectTabId, restoreLocation]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future restoreTabsByBrowserState({required RecoverableBrowserState state, required RestoreLocation restoreLocation}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.restoreTabsByBrowserState$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([state, restoreLocation]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Selects an already existing tab with the matching [HistoryMetadataKey] or otherwise + /// creates a new tab with the given [url]. + Future selectOrAddTabByHistory({required String url, required HistoryMetadataKey historyMetadata}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.selectOrAddTabByHistory$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([url, historyMetadata]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + /// Selects an already existing tab displaying [url] or otherwise creates a new tab. + Future selectOrAddTabByUrl({required String url, required bool private, required SourceValue source, required LoadUrlFlagsValue flags, required bool ignoreFragment,}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.selectOrAddTabByUrl$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([url, private, source, flags, ignoreFragment]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + Future duplicateTab({required String? selectTabId, required bool selectNewTab}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.duplicateTab$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([selectTabId, selectNewTab]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + Future moveTabs({required List tabIds, required String targetTabId, required bool placeAfter,}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.moveTabs$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabIds, targetTabId, placeAfter]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + Future migratePrivateTabUseCase({required String tabId, required String? alternativeUrl}) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTabsApi.migratePrivateTabUseCase$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([tabId, alternativeUrl]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } +} diff --git a/packages/flutter_mozilla_components/pigeons/gecko.dart b/packages/flutter_mozilla_components/pigeons/gecko.dart new file mode 100644 index 00000000..bfc5df5e --- /dev/null +++ b/packages/flutter_mozilla_components/pigeons/gecko.dart @@ -0,0 +1,414 @@ +import 'package:pigeon/pigeon.dart'; + +/// 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 { + final bool downloadModel; + + TranslationOptions({this.downloadModel = true}); +} + +/// Value type that represents the state of reader mode/view. +class ReaderState { + /// Whether or not the current page can be transformed to + /// be displayed in a reader view. + final bool readerable; + + /// Whether or not reader view is active. + final bool active; + + /// Whether or not a readerable check is required for the + /// current page. + final bool checkRequired; + + /// Whether or not a new connection to the reader view + /// content script is required. + final bool connectRequired; + + /// The base URL of the reader view extension page. + final String? baseUrl; + + /// The URL of the page currently displayed in reader view. + final String? activeUrl; + + /// The vertical scroll position of the page currently + /// displayed in reader view. + final int? scrollY; + + const ReaderState({ + this.readerable = false, + this.active = false, + this.checkRequired = false, + this.connectRequired = false, + this.baseUrl, + this.activeUrl, + this.scrollY, + }); +} + +/// Details about the last playing media in this tab. +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. + final String lastMediaUrl; + + /// 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. + final int lastMediaAccess; + + /// 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. + final bool mediaSessionActive; + + const LastMediaAccessState({ + this.lastMediaUrl = "", + this.lastMediaAccess = 0, + this.mediaSessionActive = false, + }); +} + +/// 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. +class HistoryMetadataKey { + /// A url of the page. + final String url; + + /// An optional search term if this record was + /// created as part of a search by the user. + final String? searchTerm; + + /// An optional url of the parent/referrer if + /// this record was created in response to a user opening + /// a page in a new tab. + final String? referrerUrl; + + const HistoryMetadataKey({ + required this.url, + this.searchTerm, + this.referrerUrl, + }); +} + +class PackageCategoryValue { + final int value; + + const PackageCategoryValue(this.value); +} + +/// Describes an external package. +class ExternalPackage { + /// An Android package id. + final String packageId; + + /// A [PackageCategory] as defined by the application. + final PackageCategoryValue category; + + const ExternalPackage(this.packageId, this.category); +} + +class LoadUrlFlagsValue { + final int value; + + const LoadUrlFlagsValue(this.value); +} + +class SourceValue { + final int id; + final ExternalPackage? caller; + + const SourceValue(this.id, this.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. +class TabState { + /// Unique ID identifying this tab. + final String id; + + /// The last URL of this tab. + final String url; + + /// The unique ID of the parent tab if this tab was opened from another tab (e.g. via + /// the context menu). + final String? parentId; + + /// The last title of this tab (or an empty String). + final String title; + + /// The last used search terms, or an empty string if no + /// search was executed for this session. + final String searchTerm; + + /// The context ID ("container") this tab used (or null). + final String? contextId; + + /// The last [ReaderState] of the tab. + final ReaderState readerState; + + /// The last time this tab was selected. + final int lastAccess; + + /// Timestamp of the tab's creation. + final int createdAt; + + /// Details about the last time was playing in this tab. + final LastMediaAccessState lastMediaAccessState; + + /// If tab was private. + final bool private; + + /// The last [HistoryMetadataKey] of the tab. + final HistoryMetadataKey? historyMetadata; + + /// The last [Source] of the tab. + final SourceValue source; + + /// The index the tab should be restored at. + final int index; + + /// Whether the tab has form data. + final bool hasFormData; + + TabState({ + required this.id, + required this.url, + this.parentId, + this.title = "", + this.searchTerm = "", + this.contextId, + this.readerState = const ReaderState(), + this.lastAccess = 0, + this.createdAt = 0, + this.lastMediaAccessState = const LastMediaAccessState(), + this.private = false, + this.historyMetadata, + required this.source, //= Internal.none, + this.index = -1, + this.hasFormData = false, + }); +} + +/// A recoverable version of [TabState]. +class RecoverableTab { + /// The [EngineSessionState] needed for restoring the previous state of this tab. + final String? engineSessionStateJson; + + /// A [TabState] instance containing basic tab state. + final TabState state; + + const RecoverableTab({ + this.engineSessionStateJson, + required this.state, + }); +} + +/// A restored browser state, read from disk. +class RecoverableBrowserState { + /// The list of restored tabs. + final List tabs; + + /// The ID of the selected tab in [tabs]. Or `null` if no selection was restored. + final String? selectedTabId; + + const RecoverableBrowserState({ + required this.tabs, + this.selectedTabId, + }); +} + +/// 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, +} + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/pigeons/gecko.g.dart', + dartOptions: DartOptions(), + kotlinOut: + 'android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt', + kotlinOptions: KotlinOptions(), + dartPackageName: 'flutter_mozilla_components', +)) +@HostApi() +abstract class GeckoBrowserApi { + void showNativeFragment(); +} + +@HostApi() +abstract class GeckoSessionApi { + void loadUrl({ + required String? tabId, //If null = current tab + required String url, + required LoadUrlFlagsValue flags, //== LoadUrlFlagsBuilder.NONE, + required Map? additionalHeaders, + }); + + void loadData({ + required String? tabId, //If null = current tab + required String data, + required String mimeType, + required String encoding, + }); + + void reload({ + required String? tabId, //If null = current tab + required LoadUrlFlagsValue flags, //== LoadUrlFlagsBuilder.NONE, + }); + + void stopLoading({ + required String? tabId, //If null = current tab + }); + + void goBack({ + required String? tabId, //If null = current tab + required bool userInteraction, + }); + + void goForward({ + required String? tabId, //If null = current tab + required bool userInteraction, + }); + + void goToHistoryIndex({ + required int index, + required String? tabId, //If null = current tab + }); + + void requestDesktopSite({ + required String? tabId, //If null = current tab + required bool enable, + }); + + void exitFullscreen({ + required String? tabId, //If null = current tab + }); + + void saveToPdf({ + required String? tabId, //If null = current tab + }); + + void printContent({ + required String? tabId, //If null = current tab + }); + + void translate({ + required String? tabId, //If null = current tab + required String fromLanguage, + required String toLanguage, + required TranslationOptions? options, + }); + + void translateRestore({ + required String? tabId, //If null = current tab + }); + + void crashRecovery({ + required List? tabIds, + }); + + void purgeHistory(); + + void updateLastAccess({ + required String? tabId, //If null = current tab + required int? lastAccess, //If null datetime.now + }); +} + +@HostApi() +abstract class GeckoTabsApi { + void selectTab({required String tabId}); + + void removeTab({required String tabId}); + + String addTab({ + required String url, + required bool selectTab, + required bool startLoading, + required String? parentId, + required LoadUrlFlagsValue flags, + required String? contextId, + //engineSession: EngineSession? = null, + required SourceValue source, //Internal.NewTab + //searchTerms: String = "", + required bool private, + required HistoryMetadataKey? historyMetadata, + //isSearch: Boolean = false, + //searchEngineName: String? = null, + required Map? additionalHeaders, + }); + + void removeAllTabs({required bool recoverable}); + + void removeTabs({required List ids}); + + void removeNormalTabs(); + + void removePrivateTabs(); + + void undo(); + + //restoreTabs invokes splitted + void restoreTabsByList({ + required List tabs, + required String? selectTabId, + required RestoreLocation restoreLocation, + }); + void restoreTabsByBrowserState({ + required RecoverableBrowserState state, + required 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]. + String selectOrAddTabByHistory({ + required String url, + required HistoryMetadataKey historyMetadata, + }); + + /// Selects an already existing tab displaying [url] or otherwise creates a new tab. + String selectOrAddTabByUrl({ + required String url, + required bool private, + required SourceValue source, // = Internal.newTab, + required LoadUrlFlagsValue flags, + required bool ignoreFragment, + }); + + String duplicateTab({ + required String? selectTabId, + required bool selectNewTab, + }); + + void moveTabs({ + required List tabIds, + required String targetTabId, + required bool placeAfter, + }); + + String migratePrivateTabUseCase({ + required String tabId, + required String? alternativeUrl, + }); +} diff --git a/packages/flutter_mozilla_components/pubspec.yaml b/packages/flutter_mozilla_components/pubspec.yaml new file mode 100644 index 00000000..e7fed88f --- /dev/null +++ b/packages/flutter_mozilla_components/pubspec.yaml @@ -0,0 +1,71 @@ +name: flutter_mozilla_components +description: "A new Flutter plugin project." +version: 0.0.1 +homepage: + +environment: + sdk: ^3.6.0-198.0.dev + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + pigeon: ^22.3.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: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: eu.lensai.flutter_mozilla_components + pluginClass: FlutterMozillaComponentsPlugin + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/to/asset-from-package + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # To add custom fonts to your plugin package, 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 in packages, see + # https://flutter.dev/to/font-from-package