diff --git a/buildSrc/src/main/java/Gecko.kt b/buildSrc/src/main/java/Gecko.kt index 239a70f6569..97d1d66e1d0 100644 --- a/buildSrc/src/main/java/Gecko.kt +++ b/buildSrc/src/main/java/Gecko.kt @@ -6,17 +6,17 @@ internal object GeckoVersions { /** * GeckoView Nightly Version. */ - const val nightly_version = "70.0.20190901094958" + const val nightly_version = "71.0.20190903094847" /** * GeckoView Beta Version. */ - const val beta_version = "69.0.20190824140027" + const val beta_version = "70.0.20190902085437" /** * GeckoView Release Version. */ - const val release_version = "68.0.20190711090008" + const val release_version = "69.0.20190903125908" } @Suppress("MaxLineLength") diff --git a/components/browser/engine-gecko-beta/build.gradle b/components/browser/engine-gecko-beta/build.gradle index 4ff801fc4e8..1d2bfba6c8d 100644 --- a/components/browser/engine-gecko-beta/build.gradle +++ b/components/browser/engine-gecko-beta/build.gradle @@ -2,6 +2,10 @@ * 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/. */ +plugins { + id "com.jetbrains.python.envs" version "0.0.26" +} + apply plugin: 'com.android.library' apply plugin: 'kotlin-android' @@ -26,6 +30,11 @@ android { } } +// Set configuration for the Glean parser to extract metrics.yaml +// file from AAR dependencies of this project rather than look +// for it into the project directory. +ext.allowMetricsFromAAR = true + dependencies { implementation project(':concept-engine') implementation project(':concept-fetch') @@ -40,17 +49,24 @@ dependencies { testImplementation Dependencies.androidx_test_core testImplementation Dependencies.androidx_test_junit testImplementation Dependencies.testing_robolectric + testImplementation Dependencies.testing_coroutines testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_mockwebserver - testImplementation Dependencies.testing_coroutines testImplementation project(':support-test') testImplementation project(':tooling-fetch-tests') + // We only compile against Glean. It's up to the app to add those dependencies + // if it wants to collect GeckoView telemetry through the Glean SDK. + compileOnly project(":service-glean") + testImplementation project(":service-glean") + testImplementation Dependencies.androidx_work_testing + androidTestImplementation Dependencies.androidx_test_core androidTestImplementation Dependencies.androidx_test_runner androidTestImplementation Dependencies.androidx_test_rules androidTestImplementation project(':tooling-fetch-tests') } +apply from: '../../../components/service/glean/scripts/sdk_generator.gradle' apply from: '../../../publish.gradle' ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description) diff --git a/components/browser/engine-gecko-beta/metrics.yaml b/components/browser/engine-gecko-beta/metrics.yaml new file mode 100644 index 00000000000..71bd78cb87a --- /dev/null +++ b/components/browser/engine-gecko-beta/metrics.yaml @@ -0,0 +1,29 @@ +# 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/. + +# IMPORTANT NOTE: this file is here only as a safety measure, to make +# sure the correct code is generated even though the GeckoView AAR file +# reports an empty metrics.yaml file. The metric in this file is currently +# disabled and not supposed to collect any data. + +$schema: moz://mozilla.org/schemas/glean/metrics/1-0-0 + +test.glean.geckoview: + streaming: + type: timing_distribution + gecko_datapoint: TELEMETRY_TEST_STREAMING + disabled: true + description: | + A test-only, disabled metric. This is required to guarantee + that a `GleanGeckoHistogramMapping` is always generated, even + though the GeckoView AAR exports no metric. Please note that + the data-review field below contains no review, since this + metric is disabled and not allowed to collect any data. + bugs: + - 1566374 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1566374 + notification_emails: + - glean-team@mozilla.com + expires: never diff --git a/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt b/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt index 1542c0db5e3..39bebf18956 100644 --- a/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt +++ b/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt @@ -12,8 +12,8 @@ import mozilla.components.browser.engine.gecko.mediaquery.toGeckoValue import mozilla.components.browser.engine.gecko.webextension.GeckoWebExtension import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.EngineSession -import mozilla.components.concept.engine.EngineSession.SafeBrowsingPolicy import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy +import mozilla.components.concept.engine.EngineSession.SafeBrowsingPolicy import mozilla.components.concept.engine.EngineSessionState import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.Settings @@ -160,24 +160,15 @@ class GeckoEngine( override var safeBrowsingPolicy: Array = arrayOf(SafeBrowsingPolicy.RECOMMENDED) set(value) { - val safeBrowsingCategories = value.sumBy { it.id } - val trackingCategories = - trackingProtectionPolicy?.trackingCategories?.sumBy { it.id } - ?: SafeBrowsingPolicy.NONE.id - - runtime.settings.contentBlocking.categories = - safeBrowsingCategories + trackingCategories + val policy = value.sumBy { it.id } + runtime.settings.contentBlocking.setSafeBrowsing(policy) field = value } override var trackingProtectionPolicy: TrackingProtectionPolicy? = null set(value) { value?.let { policy -> - - val trackingCategories = policy.trackingCategories.sumBy { it.id } + - safeBrowsingPolicy.sumBy { it.id } - - runtime.settings.contentBlocking.categories = trackingCategories + runtime.settings.contentBlocking.setAntiTracking(policy.trackingCategories.sumBy { it.id }) runtime.settings.contentBlocking.cookieBehavior = policy.cookiePolicy.id defaultSettings?.trackingProtectionPolicy = value field = value @@ -251,6 +242,7 @@ class GeckoEngine( this.automaticFontSizeAdjustment = it.automaticFontSizeAdjustment this.automaticLanguageAdjustment = it.automaticLanguageAdjustment this.trackingProtectionPolicy = it.trackingProtectionPolicy + this.safeBrowsingPolicy = arrayOf(SafeBrowsingPolicy.RECOMMENDED) this.remoteDebuggingEnabled = it.remoteDebuggingEnabled this.testingModeEnabled = it.testingModeEnabled this.userAgentString = it.userAgentString diff --git a/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt b/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt index 978fed2c50c..0368bfd6778 100644 --- a/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt +++ b/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt @@ -8,10 +8,13 @@ import android.annotation.SuppressLint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import mozilla.components.browser.engine.gecko.media.GeckoMediaDelegate import mozilla.components.browser.engine.gecko.permission.GeckoPermissionRequest import mozilla.components.browser.engine.gecko.prompt.GeckoPromptDelegate +import mozilla.components.browser.engine.gecko.window.GeckoWindowRequest import mozilla.components.browser.errorpages.ErrorType import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineSessionState @@ -27,7 +30,6 @@ import mozilla.components.support.ktx.android.util.Base64 import mozilla.components.support.ktx.kotlin.isEmail import mozilla.components.support.ktx.kotlin.isGeoLocation import mozilla.components.support.ktx.kotlin.isPhone -import mozilla.components.support.utils.DownloadUtils import org.json.JSONObject import org.mozilla.geckoview.AllowOrDeny import org.mozilla.geckoview.ContentBlocking @@ -53,7 +55,8 @@ class GeckoEngineSession( .build() GeckoSession(settings) }, - private val context: CoroutineContext = Dispatchers.IO + private val context: CoroutineContext = Dispatchers.IO, + openGeckoSession: Boolean = true ) : CoroutineScope, EngineSession() { internal lateinit var geckoSession: GeckoSession @@ -85,7 +88,7 @@ class GeckoEngineSession( get() = context + job init { - createGeckoSession() + createGeckoSession(shouldOpen = openGeckoSession) } /** @@ -302,11 +305,8 @@ class GeckoEngineSession( session: GeckoSession, request: NavigationDelegate.LoadRequest ): GeckoResult { - // TODO use onNewSession and create window request: - // https://github.com/mozilla-mobile/android-components/issues/1503 if (request.target == GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW) { - geckoSession.loadUri(request.uri) - return GeckoResult.fromValue(AllowOrDeny.DENY) + return GeckoResult.fromValue(AllowOrDeny.ALLOW) } val response = settings.requestInterceptor?.onLoadRequest( @@ -348,7 +348,15 @@ class GeckoEngineSession( override fun onNewSession( session: GeckoSession, uri: String - ): GeckoResult = GeckoResult.fromValue(null) + ): GeckoResult { + val newEngineSession = GeckoEngineSession(runtime, privateMode, defaultSettings, openGeckoSession = false) + notifyObservers { + MainScope().launch { + onOpenWindowRequest(GeckoWindowRequest(uri, newEngineSession)) + } + } + return GeckoResult.fromValue(newEngineSession.geckoSession) + } override fun onLoadError( session: GeckoSession, @@ -530,13 +538,11 @@ class GeckoEngineSession( override fun onExternalResponse(session: GeckoSession, response: GeckoSession.WebResponseInfo) { notifyObservers { - val fileName = response.filename - ?: DownloadUtils.guessFileName(null, response.uri, response.contentType) onExternalResource( url = response.uri, contentLength = response.contentLength, contentType = response.contentType, - fileName = fileName) + fileName = response.filename) } } @@ -571,38 +577,76 @@ class GeckoEngineSession( onTrackerBlocked(event.toTracker()) } } + + override fun onContentLoaded(session: GeckoSession, event: ContentBlocking.BlockEvent) { + notifyObservers { + onTrackerLoaded(event.toTracker()) + } + } } private fun ContentBlocking.BlockEvent.toTracker(): Tracker { val blockedContentCategories = mutableListOf() - if (categories.contains(ContentBlocking.AT_AD)) { + if (antiTrackingCategory.contains(ContentBlocking.AntiTracking.AD)) { blockedContentCategories.add(TrackingProtectionPolicy.TrackingCategory.AD) } - if (categories.contains(ContentBlocking.AT_ANALYTIC)) { + if (antiTrackingCategory.contains(ContentBlocking.AntiTracking.ANALYTIC)) { blockedContentCategories.add(TrackingProtectionPolicy.TrackingCategory.ANALYTICS) } - if (categories.contains(ContentBlocking.AT_SOCIAL)) { + if (antiTrackingCategory.contains(ContentBlocking.AntiTracking.SOCIAL)) { blockedContentCategories.add(TrackingProtectionPolicy.TrackingCategory.SOCIAL) } - if (categories.contains(ContentBlocking.AT_FINGERPRINTING)) { + if (antiTrackingCategory.contains(ContentBlocking.AntiTracking.FINGERPRINTING)) { blockedContentCategories.add(TrackingProtectionPolicy.TrackingCategory.FINGERPRINTING) } - if (categories.contains(ContentBlocking.AT_CRYPTOMINING)) { + if (antiTrackingCategory.contains(ContentBlocking.AntiTracking.CRYPTOMINING)) { blockedContentCategories.add(TrackingProtectionPolicy.TrackingCategory.CRYPTOMINING) } - if (categories.contains(ContentBlocking.AT_CONTENT)) { + + if (antiTrackingCategory.contains(ContentBlocking.AntiTracking.CONTENT)) { blockedContentCategories.add(TrackingProtectionPolicy.TrackingCategory.CONTENT) } - if (categories.contains(ContentBlocking.AT_TEST)) { + if (antiTrackingCategory.contains(ContentBlocking.AntiTracking.TEST)) { blockedContentCategories.add(TrackingProtectionPolicy.TrackingCategory.TEST) } - return Tracker(uri, blockedContentCategories) + + return Tracker( + url = uri, + trackingCategories = blockedContentCategories, + cookiePolicies = getCookiePolicies() + ) + } + + private fun ContentBlocking.BlockEvent.getCookiePolicies(): List { + val cookiesPolicies = mutableListOf() + + if (cookieBehaviorCategory == ContentBlocking.CookieBehavior.ACCEPT_ALL) { + cookiesPolicies.add(TrackingProtectionPolicy.CookiePolicy.ACCEPT_ALL) + } + + if (cookieBehaviorCategory.contains(ContentBlocking.CookieBehavior.ACCEPT_FIRST_PARTY)) { + cookiesPolicies.add(TrackingProtectionPolicy.CookiePolicy.ACCEPT_ONLY_FIRST_PARTY) + } + + if (cookieBehaviorCategory.contains(ContentBlocking.CookieBehavior.ACCEPT_NONE)) { + cookiesPolicies.add(TrackingProtectionPolicy.CookiePolicy.ACCEPT_NONE) + } + + if (cookieBehaviorCategory.contains(ContentBlocking.CookieBehavior.ACCEPT_NON_TRACKERS)) { + cookiesPolicies.add(TrackingProtectionPolicy.CookiePolicy.ACCEPT_NON_TRACKERS) + } + + if (cookieBehaviorCategory.contains(ContentBlocking.CookieBehavior.ACCEPT_VISITED)) { + cookiesPolicies.add(TrackingProtectionPolicy.CookiePolicy.ACCEPT_VISITED) + } + + return cookiesPolicies } private operator fun Int.contains(mask: Int): Boolean { @@ -689,7 +733,7 @@ class GeckoEngineSession( } } - private fun createGeckoSession() { + private fun createGeckoSession(shouldOpen: Boolean = true) { this.geckoSession = geckoSessionProvider() defaultSettings?.trackingProtectionPolicy?.let { enableTrackingProtection(it) } @@ -699,7 +743,9 @@ class GeckoEngineSession( defaultSettings?.userAgentString?.let { geckoSession.settings.userAgentOverride = it } defaultSettings?.suspendMediaWhenInactive?.let { geckoSession.settings.suspendMediaWhenInactive = it } - geckoSession.open(runtime) + if (shouldOpen) { + geckoSession.open(runtime) + } geckoSession.navigationDelegate = createNavigationDelegate() geckoSession.progressDelegate = createProgressDelegate() diff --git a/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/glean/GeckoAdapter.kt b/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/glean/GeckoAdapter.kt new file mode 100644 index 00000000000..8b852ff92a3 --- /dev/null +++ b/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/glean/GeckoAdapter.kt @@ -0,0 +1,27 @@ +/* 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 mozilla.components.browser.engine.gecko.glean + +import mozilla.components.browser.engine.gecko.GleanMetrics.GleanGeckoHistogramMapping +import org.mozilla.geckoview.RuntimeTelemetry + +/** + * This implements a [RuntimeTelemetry.Delegate] that dispatches Gecko runtime + * telemetry to the Glean SDK. + * + * Metrics defined in the `metrics.yaml` file in Gecko's mozilla-central repository + * will be automatically dispatched to the Glean SDK and sent through the requested + * pings. + * + * This can be used, in products collecting data through the Glean SDK, by + * providing an instance to `GeckoRuntimeSettings.Builder().telemetryDelegate`. + */ +class GeckoAdapter : RuntimeTelemetry.Delegate { + override fun onTelemetryReceived(metric: RuntimeTelemetry.Metric) { + // Note that the `GleanGeckoHistogramMapping` is automatically generated at + // build time by the Glean SDK parsers. + GleanGeckoHistogramMapping[metric.name]?.accumulateSamples(metric.values) + } +} diff --git a/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt b/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt index 3297fae4792..bb4639b262b 100644 --- a/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt +++ b/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt @@ -12,43 +12,21 @@ import androidx.annotation.VisibleForTesting import mozilla.components.browser.engine.gecko.GeckoEngineSession import mozilla.components.concept.engine.prompt.Choice import mozilla.components.concept.engine.prompt.PromptRequest -import mozilla.components.concept.engine.prompt.PromptRequest.Alert -import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Level -import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Method import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice -import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.ktx.kotlin.toDate import org.mozilla.geckoview.AllowOrDeny import org.mozilla.geckoview.GeckoResult import org.mozilla.geckoview.GeckoSession -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AlertCallback -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthCallback -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_CROSS_ORIGIN_SUB_RESOURCE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_HOST -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_ONLY_PASSWORD -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_PREVIOUS_FAILED -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_LEVEL_NONE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_LEVEL_PW_ENCRYPTED -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_LEVEL_SECURE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.BUTTON_TYPE_NEGATIVE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.BUTTON_TYPE_NEUTRAL -import org.mozilla.geckoview.GeckoSession.PromptDelegate.BUTTON_TYPE_POSITIVE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.ButtonCallback -import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_MENU -import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_MULTIPLE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_SINGLE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.ChoiceCallback -import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_DATE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_DATETIME_LOCAL -import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_MONTH -import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_TIME -import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_WEEK -import org.mozilla.geckoview.GeckoSession.PromptDelegate.FileCallback -import org.mozilla.geckoview.GeckoSession.PromptDelegate.TextCallback +import org.mozilla.geckoview.GeckoSession.PromptDelegate +import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.DATE +import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.DATETIME_LOCAL +import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.MONTH +import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.TIME +import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.WEEK +import org.mozilla.geckoview.GeckoSession.PromptDelegate.PromptResponse import java.io.FileOutputStream import java.io.IOException import java.security.InvalidParameterException @@ -56,96 +34,118 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -typealias GeckoChoice = GeckoSession.PromptDelegate.Choice +typealias GeckoAuthOptions = PromptDelegate.AuthPrompt.AuthOptions +typealias GeckoChoice = PromptDelegate.ChoicePrompt.Choice +typealias GECKO_AUTH_FLAGS = PromptDelegate.AuthPrompt.AuthOptions.Flags +typealias GECKO_AUTH_LEVEL = PromptDelegate.AuthPrompt.AuthOptions.Level +typealias GECKO_PROMPT_FILE_TYPE = PromptDelegate.FilePrompt.Type +typealias GECKO_PROMPT_CHOICE_TYPE = PromptDelegate.ChoicePrompt.Type +typealias GECKO_PROMPT_FILE_CAPTURE = PromptDelegate.FilePrompt.Capture +typealias AC_AUTH_LEVEL = PromptRequest.Authentication.Level +typealias AC_AUTH_METHOD = PromptRequest.Authentication.Method +typealias AC_FILE_FACING_MODE = PromptRequest.File.FacingMode /** * Gecko-based PromptDelegate implementation. */ @Suppress("TooManyFunctions") internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSession) : - GeckoSession.PromptDelegate { + PromptDelegate { override fun onChoicePrompt( session: GeckoSession, - title: String?, - msg: String?, - type: Int, - geckoChoices: Array, - callback: ChoiceCallback - ) { - val choices = convertToChoices(geckoChoices) + geckoPrompt: PromptDelegate.ChoicePrompt + ): GeckoResult? { + val geckoResult = GeckoResult() + val choices = convertToChoices(geckoPrompt.choices) val onConfirmSingleChoice: (Choice) -> Unit = { selectedChoice -> - callback.confirm(selectedChoice.id) + geckoResult.complete(geckoPrompt.confirm(selectedChoice.id)) } val onConfirmMultipleSelection: (Array) -> Unit = { selectedChoices -> val ids = selectedChoices.toIdsArray() - callback.confirm(ids) + geckoResult.complete(geckoPrompt.confirm(ids)) } - val promptRequest = when (type) { - CHOICE_TYPE_SINGLE -> SingleChoice(choices, onConfirmSingleChoice) - CHOICE_TYPE_MENU -> MenuChoice(choices, onConfirmSingleChoice) - CHOICE_TYPE_MULTIPLE -> MultipleChoice(choices, onConfirmMultipleSelection) - else -> throw InvalidParameterException("$type is not a valid Gecko @Choice.ChoiceType") + val promptRequest = when (geckoPrompt.type) { + GECKO_PROMPT_CHOICE_TYPE.SINGLE -> SingleChoice( + choices, + onConfirmSingleChoice + ) + GECKO_PROMPT_CHOICE_TYPE.MENU -> MenuChoice( + choices, + onConfirmSingleChoice + ) + GECKO_PROMPT_CHOICE_TYPE.MULTIPLE -> MultipleChoice( + choices, + onConfirmMultipleSelection + ) + else -> throw InvalidParameterException("${geckoPrompt.type} is not a valid Gecko @Choice.ChoiceType") } geckoEngineSession.notifyObservers { onPromptRequest(promptRequest) } + + return geckoResult } - override fun onAlert( + override fun onAlertPrompt( session: GeckoSession, - title: String?, - message: String?, - callback: AlertCallback - ) { - - val hasShownManyDialogs = callback.hasCheckbox() - val onDismiss: () -> Unit = { - callback.dismiss() - } + prompt: PromptDelegate.AlertPrompt + ): GeckoResult { + val geckoResult = GeckoResult() + val onConfirm: () -> Unit = { geckoResult.complete(prompt.dismiss()) } + val title = prompt.title ?: "" + val message = prompt.message ?: "" geckoEngineSession.notifyObservers { - onPromptRequest(Alert(title ?: "", message ?: "", hasShownManyDialogs, onDismiss) { showMoreDialogs -> - callback.checkboxValue = showMoreDialogs - callback.dismiss() - }) + onPromptRequest( + PromptRequest.Alert( + title, + message, + false, + onConfirm + ) { _ -> + onConfirm() + }) } + return geckoResult } override fun onFilePrompt( session: GeckoSession, - title: String?, - selectionType: Int, - mimeTypes: Array?, - callback: FileCallback - ) { + prompt: PromptDelegate.FilePrompt + ): GeckoResult? { + val geckoResult = GeckoResult() + val isMultipleFilesSelection = prompt.type == GECKO_PROMPT_FILE_TYPE.MULTIPLE + + val captureMode = when (prompt.capture) { + GECKO_PROMPT_FILE_CAPTURE.ANY -> AC_FILE_FACING_MODE.ANY + GECKO_PROMPT_FILE_CAPTURE.USER -> AC_FILE_FACING_MODE.FRONT_CAMERA + GECKO_PROMPT_FILE_CAPTURE.ENVIRONMENT -> AC_FILE_FACING_MODE.BACK_CAMERA + else -> AC_FILE_FACING_MODE.NONE + } val onSelectMultiple: (Context, Array) -> Unit = { context, uris -> val filesUris = uris.map { it.toFileUri(context) }.toTypedArray() - callback.confirm(context, filesUris) + geckoResult.complete(prompt.confirm(context, filesUris)) } - val isMultipleFilesSelection = selectionType == GeckoSession.PromptDelegate.FILE_TYPE_MULTIPLE - - val captureMode = PromptRequest.File.FacingMode.NONE - val onSelectSingle: (Context, Uri) -> Unit = { context, uri -> - callback.confirm(context, uri.toFileUri(context)) + geckoResult.complete(prompt.confirm(context, uri.toFileUri(context))) } val onDismiss: () -> Unit = { - callback.dismiss() + geckoResult.complete(prompt.dismiss()) } geckoEngineSession.notifyObservers { onPromptRequest( PromptRequest.File( - mimeTypes ?: emptyArray(), + prompt.mimeTypes ?: emptyArray(), isMultipleFilesSelection, captureMode, onSelectSingle, @@ -154,79 +154,77 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe ) ) } + return geckoResult } override fun onDateTimePrompt( session: GeckoSession, - title: String?, - type: Int, - value: String?, - minDate: String?, - maxDate: String?, - geckoCallback: TextCallback - ) { - val initialDateString = value ?: "" + prompt: PromptDelegate.DateTimePrompt + ): GeckoResult? { + val geckoResult = GeckoResult() + val onConfirm: (String) -> Unit = { geckoResult.complete(prompt.confirm(it)) } val onClear: () -> Unit = { - geckoCallback.confirm("") + onConfirm("") } - val format = when (type) { - DATETIME_TYPE_DATE -> "yyyy-MM-dd" - DATETIME_TYPE_MONTH -> "yyyy-MM" - DATETIME_TYPE_WEEK -> "yyyy-'W'ww" - DATETIME_TYPE_TIME -> "HH:mm" - DATETIME_TYPE_DATETIME_LOCAL -> "yyyy-MM-dd'T'HH:mm" + val initialDateString = prompt.defaultValue ?: "" + + val format = when (prompt.type) { + DATE -> "yyyy-MM-dd" + MONTH -> "yyyy-MM" + WEEK -> "yyyy-'W'ww" + TIME -> "HH:mm" + DATETIME_LOCAL -> "yyyy-MM-dd'T'HH:mm" else -> { - throw InvalidParameterException("$type is not a valid DatetimeType") + throw InvalidParameterException("${prompt.type} is not a valid DatetimeType") } } notifyDatePromptRequest( - title ?: "", + prompt.title ?: "", initialDateString, - minDate, - maxDate, + prompt.minValue, + prompt.maxValue, onClear, format, - geckoCallback + onConfirm ) + + return geckoResult } override fun onAuthPrompt( session: GeckoSession, - title: String?, - message: String?, - options: AuthOptions, - geckoCallback: AuthCallback - ) { - - val flags = options.flags - val userName = options.username ?: "" - val password = options.password ?: "" - val method = if (flags in AUTH_FLAG_HOST) Method.HOST else Method.PROXY - - val level = getAuthLevel(options) - - val onlyShowPassword = flags in AUTH_FLAG_ONLY_PASSWORD - val previousFailed = flags in AUTH_FLAG_PREVIOUS_FAILED - val isCrossOrigin = flags in AUTH_FLAG_CROSS_ORIGIN_SUB_RESOURCE - - val onConfirm: (String, String) -> Unit = { user, pass -> - if (onlyShowPassword) { - geckoCallback.confirm(pass) - } else { - geckoCallback.confirm(user, pass) + geckoPrompt: PromptDelegate.AuthPrompt + ): GeckoResult? { + val geckoResult = GeckoResult() + val title = geckoPrompt.title ?: "" + val message = geckoPrompt.message ?: "" + val flags = geckoPrompt.authOptions.flags + val userName = geckoPrompt.authOptions.username ?: "" + val password = geckoPrompt.authOptions.password ?: "" + val method = + if (flags in GECKO_AUTH_FLAGS.HOST) AC_AUTH_METHOD.HOST else AC_AUTH_METHOD.PROXY + val level = geckoPrompt.authOptions.toACLevel() + val onlyShowPassword = flags in GECKO_AUTH_FLAGS.ONLY_PASSWORD + val previousFailed = flags in GECKO_AUTH_FLAGS.PREVIOUS_FAILED + val isCrossOrigin = flags in GECKO_AUTH_FLAGS.CROSS_ORIGIN_SUB_RESOURCE + + val onConfirm: (String, String) -> Unit = + { user, pass -> + if (onlyShowPassword) { + geckoResult.complete(geckoPrompt.confirm(pass)) + } else { + geckoResult.complete(geckoPrompt.confirm(user, pass)) + } } - } - val onDismiss: () -> Unit = { - geckoCallback.dismiss() - } + val onDismiss: () -> Unit = { geckoResult.complete(geckoPrompt.dismiss()) } geckoEngineSession.notifyObservers { onPromptRequest( PromptRequest.Authentication( - title ?: "", - message ?: "", + title, + message, userName, password, method, @@ -239,63 +237,64 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe ) ) } + return geckoResult } override fun onTextPrompt( session: GeckoSession, - title: String?, - inputLabel: String?, - inputValue: String?, - callback: TextCallback - ) { - val hasShownManyDialogs = callback.hasCheckbox() - val onDismiss: () -> Unit = { - callback.dismiss() - } + prompt: PromptDelegate.TextPrompt + ): GeckoResult? { + val geckoResult = GeckoResult() + val title = prompt.title ?: "" + val inputLabel = prompt.message ?: "" + val inputValue = prompt.defaultValue ?: "" + val onDismiss: () -> Unit = { geckoResult.complete(prompt.dismiss()) } geckoEngineSession.notifyObservers { onPromptRequest( PromptRequest.TextPrompt( - title ?: "", - inputLabel ?: "", - inputValue ?: "", - hasShownManyDialogs, + title, + inputLabel, + inputValue, + false, onDismiss - ) { showMoreDialogs, valueInput -> - callback.checkboxValue = showMoreDialogs - callback.confirm(valueInput) + ) { _, valueInput -> + geckoResult.complete(prompt.confirm(valueInput)) }) } + + return geckoResult } override fun onColorPrompt( session: GeckoSession, - title: String?, - defaultColor: String?, - callback: TextCallback - ) { + prompt: PromptDelegate.ColorPrompt + ): GeckoResult? { + val geckoResult = GeckoResult() + val onConfirm: (String) -> Unit = { geckoResult.complete(prompt.confirm(it)) } + val onDismiss: () -> Unit = { geckoResult.complete(prompt.dismiss()) } + + val defaultColor = prompt.defaultValue ?: "" - val onConfirm: (String) -> Unit = { - callback.confirm(it) - } - val onDismiss: () -> Unit = { - callback.dismiss() - } geckoEngineSession.notifyObservers { onPromptRequest( - PromptRequest.Color(defaultColor ?: "", onConfirm, onDismiss) + PromptRequest.Color(defaultColor, onConfirm, onDismiss) ) } + return geckoResult } - override fun onPopupRequest(session: GeckoSession, targetUri: String?): GeckoResult { - val geckoResult = GeckoResult() - val onAllow: () -> Unit = { geckoResult.complete(AllowOrDeny.ALLOW) } - val onDeny: () -> Unit = { geckoResult.complete(AllowOrDeny.DENY) } + override fun onPopupPrompt( + session: GeckoSession, + prompt: PromptDelegate.PopupPrompt + ): GeckoResult { + val geckoResult = GeckoResult() + val onAllow: () -> Unit = { geckoResult.complete(prompt.confirm(AllowOrDeny.ALLOW)) } + val onDeny: () -> Unit = { geckoResult.complete(prompt.confirm(AllowOrDeny.DENY)) } geckoEngineSession.notifyObservers { onPromptRequest( - PromptRequest.Popup(targetUri ?: "", onAllow, onDeny) + PromptRequest.Popup(prompt.targetUri ?: "", onAllow, onDeny) ) } return geckoResult @@ -303,56 +302,47 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe override fun onButtonPrompt( session: GeckoSession, - title: String?, - message: String?, - buttonTitles: Array?, - callback: ButtonCallback - ) { - val hasShownManyDialogs = callback.hasCheckbox() - val positiveButtonTitle = buttonTitles?.get(BUTTON_TYPE_POSITIVE) ?: "" - val negativeButtonTitle = buttonTitles?.get(BUTTON_TYPE_NEGATIVE) ?: "" - val neutralButtonTitle = buttonTitles?.get(BUTTON_TYPE_NEUTRAL) ?: "" - - val onConfirmPositiveButton: (Boolean) -> Unit = { showMoreDialogs -> - callback.checkboxValue = showMoreDialogs - callback.confirm(BUTTON_TYPE_POSITIVE) - } + prompt: PromptDelegate.ButtonPrompt + ): GeckoResult? { + val geckoResult = GeckoResult() + val title = prompt.title ?: "" + val message = prompt.message ?: "" - val onConfirmNegativeButton: (Boolean) -> Unit = { showMoreDialogs -> - callback.checkboxValue = showMoreDialogs - callback.confirm(BUTTON_TYPE_NEGATIVE) + val onConfirmPositiveButton: (Boolean) -> Unit = { + geckoResult.complete(prompt.confirm(PromptDelegate.ButtonPrompt.Type.POSITIVE)) } - - val onConfirmNeutralButton: (Boolean) -> Unit = { showMoreDialogs -> - callback.checkboxValue = showMoreDialogs - callback.confirm(BUTTON_TYPE_NEUTRAL) + val onConfirmNegativeButton: (Boolean) -> Unit = { + geckoResult.complete(prompt.confirm(PromptDelegate.ButtonPrompt.Type.NEGATIVE)) } - val onDismiss: () -> Unit = { - callback.dismiss() - } + val onDismiss: (Boolean) -> Unit = { geckoResult.complete(prompt.dismiss()) } geckoEngineSession.notifyObservers { onPromptRequest( PromptRequest.Confirm( - title ?: "", - message ?: "", - hasShownManyDialogs, - positiveButtonTitle, - negativeButtonTitle, - neutralButtonTitle, + title, + message, + false, + "", + "", + "", onConfirmPositiveButton, onConfirmNegativeButton, - onConfirmNeutralButton, onDismiss - ) + ) { + onDismiss(false) + } ) } + return geckoResult } private fun GeckoChoice.toChoice(): Choice { val choiceChildren = items?.map { it.toChoice() }?.toTypedArray() - return Choice(id, !disabled, label, selected, separator, choiceChildren) + // On the GeckoView docs states that label is a @NonNull, but on run-time + // we are getting null values + @Suppress("USELESS_ELVIS") + return Choice(id, !disabled, label ?: "", selected, separator, choiceChildren) } /** @@ -377,35 +367,45 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe maxDateString: String?, onClear: () -> Unit, format: String, - geckoCallback: TextCallback + onConfirm: (String) -> Unit ) { val initialDate = initialDateString.toDate(format) val minDate = if (minDateString.isNullOrEmpty()) null else minDateString.toDate() val maxDate = if (maxDateString.isNullOrEmpty()) null else maxDateString.toDate() val onSelect: (Date) -> Unit = { val stringDate = it.toString(format) - geckoCallback.confirm(stringDate) + onConfirm(stringDate) } val selectionType = when (format) { - "HH:mm" -> TimeSelection.Type.TIME - "yyyy-MM" -> TimeSelection.Type.MONTH - "yyyy-MM-dd'T'HH:mm" -> TimeSelection.Type.DATE_AND_TIME - else -> TimeSelection.Type.DATE + "HH:mm" -> PromptRequest.TimeSelection.Type.TIME + "yyyy-MM" -> PromptRequest.TimeSelection.Type.MONTH + "yyyy-MM-dd'T'HH:mm" -> PromptRequest.TimeSelection.Type.DATE_AND_TIME + else -> PromptRequest.TimeSelection.Type.DATE } geckoEngineSession.notifyObservers { - onPromptRequest(TimeSelection(title, initialDate, minDate, maxDate, selectionType, onSelect, onClear)) + onPromptRequest( + PromptRequest.TimeSelection( + title, + initialDate, + minDate, + maxDate, + selectionType, + onSelect, + onClear + ) + ) } } - private fun getAuthLevel(options: AuthOptions): Level { - return when (options.level) { - AUTH_LEVEL_NONE -> Level.NONE - AUTH_LEVEL_PW_ENCRYPTED -> Level.PASSWORD_ENCRYPTED - AUTH_LEVEL_SECURE -> Level.SECURED + private fun GeckoAuthOptions.toACLevel(): AC_AUTH_LEVEL { + return when (level) { + GECKO_AUTH_LEVEL.NONE -> AC_AUTH_LEVEL.NONE + GECKO_AUTH_LEVEL.PW_ENCRYPTED -> AC_AUTH_LEVEL.PASSWORD_ENCRYPTED + GECKO_AUTH_LEVEL.SECURE -> AC_AUTH_LEVEL.SECURED else -> { - Level.NONE + AC_AUTH_LEVEL.NONE } } } diff --git a/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/window/GeckoWindowRequest.kt b/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/window/GeckoWindowRequest.kt new file mode 100644 index 00000000000..dde6cc8d64f --- /dev/null +++ b/components/browser/engine-gecko-beta/src/main/java/mozilla/components/browser/engine/gecko/window/GeckoWindowRequest.kt @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.browser.engine.gecko.window + +import mozilla.components.browser.engine.gecko.GeckoEngineSession +import mozilla.components.concept.engine.EngineSession +import mozilla.components.concept.engine.window.WindowRequest + +/** + * Gecko-based implementation of [WindowRequest]. + */ +class GeckoWindowRequest( + override val url: String, + private val engineSession: GeckoEngineSession +) : WindowRequest { + + override fun prepare(): EngineSession { + return this.engineSession + } +} diff --git a/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt b/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt index 87e30497433..8bc2c1e2196 100644 --- a/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt +++ b/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt @@ -15,6 +15,7 @@ import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineSession.LoadUrlFlags import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.CookiePolicy import mozilla.components.concept.engine.EngineSession.SafeBrowsingPolicy import mozilla.components.concept.engine.HitResult import mozilla.components.concept.engine.UnsupportedSettingException @@ -23,6 +24,7 @@ import mozilla.components.concept.engine.history.HistoryTrackingDelegate import mozilla.components.concept.engine.manifest.WebAppManifest import mozilla.components.concept.engine.permission.PermissionRequest import mozilla.components.concept.engine.request.RequestInterceptor +import mozilla.components.concept.engine.window.WindowRequest import mozilla.components.concept.storage.VisitType import mozilla.components.support.test.any import mozilla.components.support.test.eq @@ -66,6 +68,9 @@ import org.mozilla.geckoview.WebRequestError import org.mozilla.geckoview.WebRequestError.ERROR_CATEGORY_UNKNOWN import org.mozilla.geckoview.WebRequestError.ERROR_MALFORMED_URI import org.mozilla.geckoview.WebRequestError.ERROR_UNKNOWN +typealias GeckoAntiTracking = ContentBlocking.AntiTracking +typealias GeckoSafeBrowsing = ContentBlocking.SafeBrowsing +typealias GeckoCookieBehavior = ContentBlocking.CookieBehavior @ExperimentalCoroutinesApi @RunWith(AndroidJUnit4::class) @@ -784,8 +789,10 @@ class GeckoEngineSessionTest { @Test fun trackingProtectionDelegateNotifiesObservers() { - val engineSession = GeckoEngineSession(mock(), - geckoSessionProvider = geckoSessionProvider) + val engineSession = GeckoEngineSession( + mock(), + geckoSessionProvider = geckoSessionProvider + ) var trackerBlocked: Tracker? = null engineSession.register(object : EngineSession.Observer { @@ -795,19 +802,20 @@ class GeckoEngineSessionTest { }) captureDelegates() - - var geckoCatgories = 0 - geckoCatgories = geckoCatgories.or(ContentBlocking.AT_AD) - geckoCatgories = geckoCatgories.or(ContentBlocking.AT_ANALYTIC) - geckoCatgories = geckoCatgories.or(ContentBlocking.AT_SOCIAL) - geckoCatgories = geckoCatgories.or(ContentBlocking.AT_CRYPTOMINING) - geckoCatgories = geckoCatgories.or(ContentBlocking.AT_FINGERPRINTING) - geckoCatgories = geckoCatgories.or(ContentBlocking.AT_CONTENT) - geckoCatgories = geckoCatgories.or(ContentBlocking.AT_TEST) - - contentBlockingDelegate.value.onContentBlocked(geckoSession, - ContentBlocking.BlockEvent("tracker1", geckoCatgories) + var geckoCategories = 0 + geckoCategories = geckoCategories.or(GeckoAntiTracking.AD) + geckoCategories = geckoCategories.or(GeckoAntiTracking.ANALYTIC) + geckoCategories = geckoCategories.or(GeckoAntiTracking.SOCIAL) + geckoCategories = geckoCategories.or(GeckoAntiTracking.CRYPTOMINING) + geckoCategories = geckoCategories.or(GeckoAntiTracking.FINGERPRINTING) + geckoCategories = geckoCategories.or(GeckoAntiTracking.CONTENT) + geckoCategories = geckoCategories.or(GeckoAntiTracking.TEST) + + contentBlockingDelegate.value.onContentBlocked( + geckoSession, + ContentBlocking.BlockEvent("tracker1", geckoCategories, 0, 0, false) ) + assertEquals("tracker1", trackerBlocked!!.url) val expectedBlockedCategories = listOf( @@ -821,6 +829,48 @@ class GeckoEngineSessionTest { ) assertTrue(trackerBlocked!!.trackingCategories.containsAll(expectedBlockedCategories)) + + var trackerLoaded: Tracker? = null + engineSession.register(object : EngineSession.Observer { + override fun onTrackerLoaded(tracker: Tracker) { + trackerLoaded = tracker + } + }) + + var geckoCookieCategories = 0 + geckoCookieCategories = geckoCookieCategories.or(GeckoCookieBehavior.ACCEPT_ALL) + geckoCookieCategories = geckoCookieCategories.or(GeckoCookieBehavior.ACCEPT_VISITED) + geckoCookieCategories = geckoCookieCategories.or(GeckoCookieBehavior.ACCEPT_NON_TRACKERS) + geckoCookieCategories = geckoCookieCategories.or(GeckoCookieBehavior.ACCEPT_NONE) + geckoCookieCategories = geckoCookieCategories.or(GeckoCookieBehavior.ACCEPT_FIRST_PARTY) + + contentBlockingDelegate.value.onContentLoaded( + geckoSession, + ContentBlocking.BlockEvent("tracker1", 0, 0, geckoCookieCategories, false) + ) + + val expectedCookieCategories = listOf( + CookiePolicy.ACCEPT_ONLY_FIRST_PARTY, + CookiePolicy.ACCEPT_NONE, + CookiePolicy.ACCEPT_VISITED, + CookiePolicy.ACCEPT_NON_TRACKERS + ) + + assertEquals("tracker1", trackerLoaded!!.url) + assertTrue(trackerLoaded!!.cookiePolicies.containsAll(expectedCookieCategories)) + + contentBlockingDelegate.value.onContentLoaded( + geckoSession, + ContentBlocking.BlockEvent("tracker1", 0, 0, GeckoCookieBehavior.ACCEPT_ALL, false) + ) + + assertTrue( + trackerLoaded!!.cookiePolicies.containsAll( + listOf( + CookiePolicy.ACCEPT_ALL + ) + ) + ) } @Test @@ -828,7 +878,11 @@ class GeckoEngineSessionTest { val runtime = mock() whenever(runtime.settings).thenReturn(mock()) val session = GeckoEngineSession(runtime, geckoSessionProvider = geckoSessionProvider) - val privSession = GeckoEngineSession(runtime, geckoSessionProvider = geckoSessionProvider, privateMode = true) + val privSession = GeckoEngineSession( + runtime, + geckoSessionProvider = geckoSessionProvider, + privateMode = true + ) var trackerBlockingObserved = false session.register(object : EngineSession.Observer { @@ -844,7 +898,8 @@ class GeckoEngineSessionTest { }) val allPolicy = TrackingProtectionPolicy.select( - arrayOf(TrackingCategory.AD)) + trackingCategories = arrayOf(TrackingCategory.AD) + ) val regularOnlyPolicy = TrackingProtectionPolicy.select( trackingCategories = arrayOf(TrackingCategory.AD) ).forRegularSessionsOnly() @@ -890,44 +945,48 @@ class GeckoEngineSessionTest { @Test fun safeBrowsingCategoriesAreAligned() { - assertEquals(ContentBlocking.SB_MALWARE, SafeBrowsingPolicy.MALWARE.id) - assertEquals(ContentBlocking.SB_UNWANTED, SafeBrowsingPolicy.UNWANTED.id) - assertEquals(ContentBlocking.SB_HARMFUL, SafeBrowsingPolicy.HARMFUL.id) - assertEquals(ContentBlocking.SB_PHISHING, SafeBrowsingPolicy.PHISHING.id) - assertEquals(ContentBlocking.SB_ALL, SafeBrowsingPolicy.RECOMMENDED.id) + assertEquals(GeckoSafeBrowsing.NONE, SafeBrowsingPolicy.NONE.id) + assertEquals(GeckoSafeBrowsing.MALWARE, SafeBrowsingPolicy.MALWARE.id) + assertEquals(GeckoSafeBrowsing.UNWANTED, SafeBrowsingPolicy.UNWANTED.id) + assertEquals(GeckoSafeBrowsing.HARMFUL, SafeBrowsingPolicy.HARMFUL.id) + assertEquals(GeckoSafeBrowsing.PHISHING, SafeBrowsingPolicy.PHISHING.id) + assertEquals(GeckoSafeBrowsing.DEFAULT, SafeBrowsingPolicy.RECOMMENDED.id) } @Test fun trackingProtectionCategoriesAreAligned() { - assertEquals(TrackingCategory.AD.id, ContentBlocking.AT_AD) - assertEquals(TrackingCategory.ANALYTICS.id, ContentBlocking.AT_ANALYTIC) - assertEquals(TrackingCategory.CONTENT.id, ContentBlocking.AT_CONTENT) - assertEquals(TrackingCategory.SOCIAL.id, ContentBlocking.AT_SOCIAL) - assertEquals(TrackingCategory.TEST.id, ContentBlocking.AT_TEST) - assertEquals(TrackingCategory.CRYPTOMINING.id, ContentBlocking.AT_CRYPTOMINING) - assertEquals(TrackingCategory.FINGERPRINTING.id, ContentBlocking.AT_FINGERPRINTING) - assertEquals( - TrackingProtectionPolicy.strict().trackingCategories.sumBy { it.id }, - ContentBlocking.AT_STRICT - ) + assertEquals(GeckoAntiTracking.NONE, TrackingCategory.NONE.id) + assertEquals(GeckoAntiTracking.AD, TrackingCategory.AD.id) + assertEquals(GeckoAntiTracking.CONTENT, TrackingCategory.CONTENT.id) + assertEquals(GeckoAntiTracking.SOCIAL, TrackingCategory.SOCIAL.id) + assertEquals(GeckoAntiTracking.TEST, TrackingCategory.TEST.id) + assertEquals(GeckoAntiTracking.CRYPTOMINING, TrackingCategory.CRYPTOMINING.id) + assertEquals(GeckoAntiTracking.FINGERPRINTING, TrackingCategory.FINGERPRINTING.id) + assertEquals(GeckoAntiTracking.DEFAULT, TrackingCategory.RECOMMENDED.id) + assertEquals(GeckoAntiTracking.STRICT, TrackingCategory.STRICT.id) - assertEquals( - TrackingProtectionPolicy.recommended().trackingCategories.sumBy { it.id }, - ContentBlocking.AT_DEFAULT - ) + val recommendedPolicy = TrackingProtectionPolicy.recommended() + val strictPolicy = TrackingProtectionPolicy.strict() + var antiTrackingCategories = strictPolicy.trackingCategories.sumBy { it.id } - assertEquals(TrackingProtectionPolicy.CookiePolicy.ACCEPT_ALL.id, ContentBlocking.COOKIE_ACCEPT_ALL) + assertEquals(GeckoAntiTracking.STRICT, antiTrackingCategories) + + antiTrackingCategories = recommendedPolicy.trackingCategories.sumBy { it.id } + + assertEquals(GeckoAntiTracking.DEFAULT, antiTrackingCategories) + + assertEquals(GeckoCookieBehavior.ACCEPT_ALL, CookiePolicy.ACCEPT_ALL.id) assertEquals( - TrackingProtectionPolicy.CookiePolicy.ACCEPT_NON_TRACKERS.id, - ContentBlocking.COOKIE_ACCEPT_NON_TRACKERS + GeckoCookieBehavior.ACCEPT_NON_TRACKERS, + CookiePolicy.ACCEPT_NON_TRACKERS.id ) - assertEquals(TrackingProtectionPolicy.CookiePolicy.ACCEPT_NONE.id, ContentBlocking.COOKIE_ACCEPT_NONE) + assertEquals(GeckoCookieBehavior.ACCEPT_NONE, CookiePolicy.ACCEPT_NONE.id) assertEquals( - TrackingProtectionPolicy.CookiePolicy.ACCEPT_ONLY_FIRST_PARTY.id, - ContentBlocking.COOKIE_ACCEPT_FIRST_PARTY + GeckoCookieBehavior.ACCEPT_FIRST_PARTY, CookiePolicy.ACCEPT_ONLY_FIRST_PARTY.id + ) - assertEquals(TrackingProtectionPolicy.CookiePolicy.ACCEPT_VISITED.id, ContentBlocking.COOKIE_ACCEPT_VISITED) + assertEquals(GeckoCookieBehavior.ACCEPT_VISITED, CookiePolicy.ACCEPT_VISITED.id) } @Test @@ -1597,8 +1656,7 @@ class GeckoEngineSessionTest { mockLoadRequest("sample:about", GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW)) assertNotNull(result) - assertEquals(result!!.poll(0), AllowOrDeny.DENY) - verify(geckoSession).loadUri("sample:about") + assertEquals(result!!.poll(0), AllowOrDeny.ALLOW) } @Test @@ -1830,7 +1888,7 @@ class GeckoEngineSessionTest { @Test fun `onKill will recover, restore state and notify observers`() { val engineSession = GeckoEngineSession(mock(), - geckoSessionProvider = geckoSessionProvider) + geckoSessionProvider = geckoSessionProvider) captureDelegates() @@ -1853,6 +1911,26 @@ class GeckoEngineSessionTest { assertTrue(observerNotified) } + @Test + fun `onNewSession creates window request`() { + val engineSession = GeckoEngineSession(mock(), geckoSessionProvider = geckoSessionProvider) + + captureDelegates() + + var receivedWindowRequest: WindowRequest? = null + + engineSession.register(object : EngineSession.Observer { + override fun onOpenWindowRequest(windowRequest: WindowRequest) { + receivedWindowRequest = windowRequest + } + }) + + navigationDelegate.value.onNewSession(mock(), "mozilla.org") + + assertNotNull(receivedWindowRequest) + assertEquals("mozilla.org", receivedWindowRequest!!.url) + } + private fun mockGeckoSession(): GeckoSession { val session = mock() whenever(session.settings).thenReturn( diff --git a/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt b/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt index f9e2ee7a8f2..add1cdf3482 100644 --- a/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt +++ b/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt @@ -138,14 +138,16 @@ class GeckoEngineTest { engine.settings.trackingProtectionPolicy = TrackingProtectionPolicy.strict() val trackingStrictCategories = TrackingProtectionPolicy.strict().trackingCategories.sumBy { it.id } - assertEquals(trackingStrictCategories, ContentBlocking.AT_STRICT) + assertEquals(trackingStrictCategories, contentBlockingSettings.antiTrackingCategories) val safeStrictBrowsingCategories = SafeBrowsingPolicy.RECOMMENDED.id - assertEquals(safeStrictBrowsingCategories, ContentBlocking.SB_ALL) - assertEquals(contentBlockingSettings.cookieBehavior, CookiePolicy.ACCEPT_NON_TRACKERS.id) + assertEquals(safeStrictBrowsingCategories, contentBlockingSettings.safeBrowsingCategories) engine.settings.safeBrowsingPolicy = arrayOf(SafeBrowsingPolicy.PHISHING) - assertTrue(contentBlockingSettings.contains(SafeBrowsingPolicy.PHISHING)) + assertEquals(SafeBrowsingPolicy.PHISHING.id, contentBlockingSettings.safeBrowsingCategories) + + assertEquals(defaultSettings.trackingProtectionPolicy, TrackingProtectionPolicy.strict()) + assertEquals(contentBlockingSettings.cookieBehavior, CookiePolicy.ACCEPT_NON_TRACKERS.id) try { engine.settings.domStorageEnabled @@ -194,27 +196,38 @@ class GeckoEngineTest { verify(runtimeSettings).autoplayDefault = GeckoRuntimeSettings.AUTOPLAY_DEFAULT_BLOCKED val trackingStrictCategories = TrackingProtectionPolicy.strict().trackingCategories.sumBy { it.id } - assertEquals(trackingStrictCategories, ContentBlocking.AT_STRICT) + assertEquals(trackingStrictCategories, contentBlockingSettings.antiTrackingCategories) - assertTrue(contentBlockingSettings.contains(SafeBrowsingPolicy.RECOMMENDED)) + assertEquals(SafeBrowsingPolicy.RECOMMENDED.id, contentBlockingSettings.safeBrowsingCategories) - val safeStrictBrowsingCategories = SafeBrowsingPolicy.RECOMMENDED.id - assertEquals(safeStrictBrowsingCategories, ContentBlocking.SB_ALL) - - assertEquals(contentBlockingSettings.cookieBehavior, CookiePolicy.ACCEPT_NON_TRACKERS.id) + assertEquals(CookiePolicy.ACCEPT_NON_TRACKERS.id, contentBlockingSettings.cookieBehavior) assertTrue(engine.settings.testingModeEnabled) assertEquals("test-ua", engine.settings.userAgentString) assertEquals(PreferredColorScheme.Light, engine.settings.preferredColorScheme) assertFalse(engine.settings.allowAutoplayMedia) assertTrue(engine.settings.suspendMediaWhenInactive) + engine.settings.safeBrowsingPolicy = arrayOf(SafeBrowsingPolicy.PHISHING) engine.settings.trackingProtectionPolicy = TrackingProtectionPolicy.select( trackingCategories = arrayOf(TrackingProtectionPolicy.TrackingCategory.AD), cookiePolicy = CookiePolicy.ACCEPT_ONLY_FIRST_PARTY ) - assertEquals(CookiePolicy.ACCEPT_ONLY_FIRST_PARTY.id, contentBlockingSettings.cookieBehavior) + assertEquals( + TrackingProtectionPolicy.TrackingCategory.AD.id, + contentBlockingSettings.antiTrackingCategories + ) + + assertEquals( + SafeBrowsingPolicy.PHISHING.id, + contentBlockingSettings.safeBrowsingCategories + ) + + assertEquals( + CookiePolicy.ACCEPT_ONLY_FIRST_PARTY.id, + contentBlockingSettings.cookieBehavior + ) engine.settings.trackingProtectionPolicy = TrackingProtectionPolicy.none() @@ -416,7 +429,4 @@ class GeckoEngineTest { assertTrue(version.major >= 69) assertTrue(version.isAtLeast(69, 0, 0)) } - - private fun ContentBlocking.Settings.contains(vararg safeBrowsingPolicies: SafeBrowsingPolicy) = - (safeBrowsingPolicies.sumBy { it.id } and this.categories) != 0 } \ No newline at end of file diff --git a/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt b/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt index 8015574faeb..02429dd28ec 100644 --- a/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt +++ b/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt @@ -4,56 +4,47 @@ package mozilla.components.browser.engine.gecko.prompt -import android.content.Context import android.net.Uri -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import mozilla.components.browser.engine.gecko.GeckoEngineSession import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.prompt.Choice import mozilla.components.concept.engine.prompt.PromptRequest -import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Level.NONE -import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Level.PASSWORD_ENCRYPTED -import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Level.SECURED -import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Method.HOST import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice import mozilla.components.support.ktx.kotlin.toDate import mozilla.components.support.test.mock -import org.junit.Assert +import mozilla.components.support.test.robolectric.testContext +import mozilla.components.test.ReflectionUtils import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith -import org.mozilla.geckoview.AllowOrDeny -import org.mozilla.geckoview.GeckoResult +import org.mozilla.gecko.util.GeckoBundle import org.mozilla.geckoview.GeckoSession -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_CROSS_ORIGIN_SUB_RESOURCE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_HOST -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_ONLY_PASSWORD -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_PREVIOUS_FAILED -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_LEVEL_PW_ENCRYPTED -import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_LEVEL_SECURE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.BUTTON_TYPE_NEGATIVE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.BUTTON_TYPE_NEUTRAL -import org.mozilla.geckoview.GeckoSession.PromptDelegate.BUTTON_TYPE_POSITIVE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_MULTIPLE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_SINGLE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_DATE -import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_DATETIME_LOCAL -import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_MONTH -import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_TIME -import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_WEEK -import org.mozilla.geckoview.GeckoSession.PromptDelegate.TextCallback +import java.security.InvalidParameterException +import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.DATE +import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.DATETIME_LOCAL +import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.MONTH +import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.TIME +import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.WEEK +import org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.ANY +import org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.NONE +import org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.USER import org.robolectric.Shadows.shadowOf import java.io.FileInputStream -import java.security.InvalidParameterException import java.util.Calendar import java.util.Calendar.YEAR import java.util.Date - -typealias GeckoChoice = GeckoSession.PromptDelegate.Choice +typealias GeckoChoice = GeckoSession.PromptDelegate.ChoicePrompt.Choice +typealias GECKO_AUTH_LEVEL = GeckoSession.PromptDelegate.AuthPrompt.AuthOptions.Level +typealias GECKO_PROMPT_CHOICE_TYPE = GeckoSession.PromptDelegate.ChoicePrompt.Type +typealias GECKO_AUTH_FLAGS = GeckoSession.PromptDelegate.AuthPrompt.AuthOptions.Flags +typealias GECKO_PROMPT_FILE_TYPE = GeckoSession.PromptDelegate.FilePrompt.Type +typealias AC_AUTH_METHOD = PromptRequest.Authentication.Method +typealias AC_AUTH_LEVEL = PromptRequest.Authentication.Level @RunWith(AndroidJUnit4::class) class GeckoPromptDelegateTest { @@ -63,23 +54,26 @@ class GeckoPromptDelegateTest { val mockSession = GeckoEngineSession(mock()) var promptRequestSingleChoice: PromptRequest = MultipleChoice(arrayOf()) {} var confirmWasCalled = false - - val callback = object : DefaultGeckoChoiceCallback() { - override fun confirm(id: String?) { - confirmWasCalled = true - } - } - val gecko = GeckoPromptDelegate(mockSession) val geckoChoice = object : GeckoChoice() {} - val geckoChoices = arrayOf(geckoChoice) + val geckoPrompt = GeckoChoicePrompt( + "title", + "message", + GECKO_PROMPT_CHOICE_TYPE.SINGLE, + arrayOf(geckoChoice) + ) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { promptRequestSingleChoice = promptRequest } }) - gecko.onChoicePrompt(mock(), null, null, CHOICE_TYPE_SINGLE, geckoChoices, callback) + + val geckoResult = gecko.onChoicePrompt(mock(), geckoPrompt) + + geckoResult!!.accept { + confirmWasCalled = true + } assertTrue(promptRequestSingleChoice is SingleChoice) val request = promptRequestSingleChoice as SingleChoice @@ -93,16 +87,14 @@ class GeckoPromptDelegateTest { val mockSession = GeckoEngineSession(mock()) var promptRequestSingleChoice: PromptRequest = SingleChoice(arrayOf()) {} var confirmWasCalled = false - - val callback = object : DefaultGeckoChoiceCallback() { - override fun confirm(ids: Array) { - confirmWasCalled = true - } - } - val gecko = GeckoPromptDelegate(mockSession) val mockGeckoChoice = object : GeckoChoice() {} - val geckoChoices = arrayOf(mockGeckoChoice) + val geckoPrompt = GeckoChoicePrompt( + "title", + "message", + GECKO_PROMPT_CHOICE_TYPE.MULTIPLE, + arrayOf(mockGeckoChoice) + ) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { @@ -110,7 +102,11 @@ class GeckoPromptDelegateTest { } }) - gecko.onChoicePrompt(mock(), null, null, CHOICE_TYPE_MULTIPLE, geckoChoices, callback) + val geckoResult = gecko.onChoicePrompt(mock(), geckoPrompt) + + geckoResult!!.accept { + confirmWasCalled = true + } assertTrue(promptRequestSingleChoice is MultipleChoice) @@ -123,16 +119,14 @@ class GeckoPromptDelegateTest { val mockSession = GeckoEngineSession(mock()) var promptRequestSingleChoice: PromptRequest = PromptRequest.MenuChoice(arrayOf()) {} var confirmWasCalled = false - - val callback = object : DefaultGeckoChoiceCallback() { - override fun confirm(id: String?) { - confirmWasCalled = true - } - } - val gecko = GeckoPromptDelegate(mockSession) val geckoChoice = object : GeckoChoice() {} - val geckoChoices = arrayOf(geckoChoice) + val geckoPrompt = GeckoChoicePrompt( + "title", + "message", + GECKO_PROMPT_CHOICE_TYPE.MENU, + arrayOf(geckoChoice) + ) mockSession.register( object : EngineSession.Observer { @@ -141,10 +135,10 @@ class GeckoPromptDelegateTest { } }) - gecko.onChoicePrompt( - mock(), null, null, - GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_MENU, geckoChoices, callback - ) + val geckoResult = gecko.onChoicePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + confirmWasCalled = true + } assertTrue(promptRequestSingleChoice is PromptRequest.MenuChoice) val request = promptRequestSingleChoice as PromptRequest.MenuChoice @@ -156,37 +150,20 @@ class GeckoPromptDelegateTest { @Test(expected = InvalidParameterException::class) fun `calling onChoicePrompt with not valid Gecko ChoiceType will throw an exception`() { val promptDelegate = GeckoPromptDelegate(mock()) - promptDelegate.onChoicePrompt( - mock(), + val geckoPrompt = GeckoChoicePrompt( "title", "message", -1, - arrayOf(), - mock() + arrayOf() ) + promptDelegate.onChoicePrompt(mock(), geckoPrompt) } @Test - fun `onAlert must provide an alert PromptRequest`() { + fun `onAlertPrompt must provide an alert PromptRequest`() { val mockSession = GeckoEngineSession(mock()) var alertRequest: PromptRequest? = null var dismissWasCalled = false - var setCheckboxValueWasCalled = false - - val callback = object : GeckoSession.PromptDelegate.AlertCallback { - - override fun setCheckboxValue(value: Boolean) { - setCheckboxValueWasCalled = true - } - - override fun dismiss() { - dismissWasCalled = true - } - - override fun getCheckboxValue(): Boolean = false - override fun hasCheckbox(): Boolean = false - override fun getCheckboxMessage(): String = "" - } val promptDelegate = GeckoPromptDelegate(mockSession) @@ -196,31 +173,19 @@ class GeckoPromptDelegateTest { } }) - promptDelegate.onAlert(mock(), "title", "message", callback) - + val geckoResult = promptDelegate.onAlertPrompt(mock(), GeckoAlertPrompt()) + geckoResult.accept { + dismissWasCalled = true + } assertTrue(alertRequest is PromptRequest.Alert) (alertRequest as PromptRequest.Alert).onDismiss() assertTrue(dismissWasCalled) - (alertRequest as PromptRequest.Alert).onConfirm(true) - assertTrue(setCheckboxValueWasCalled) - assertEquals((alertRequest as PromptRequest.Alert).title, "title") assertEquals((alertRequest as PromptRequest.Alert).message, "message") } - @Test - fun `hitting default values`() { - val mockSession = GeckoEngineSession(mock()) - val gecko = GeckoPromptDelegate(mockSession) - gecko.onDateTimePrompt(mock(), null, DATETIME_TYPE_DATE, null, null, null, mock()) - gecko.onDateTimePrompt(mock(), null, DATETIME_TYPE_WEEK, null, null, null, mock()) - gecko.onDateTimePrompt(mock(), null, DATETIME_TYPE_MONTH, null, null, null, mock()) - gecko.onDateTimePrompt(mock(), null, DATETIME_TYPE_TIME, null, "", "", mock()) - gecko.onButtonPrompt(mock(), null, null, arrayOf(null, null, null), mock()) - } - @Test fun `toIdsArray must convert an list of choices to array of id strings`() { val choices = arrayOf(Choice(id = "0", label = ""), Choice(id = "1", label = "")) @@ -236,32 +201,31 @@ class GeckoPromptDelegateTest { var dateRequest: PromptRequest? = null var confirmCalled = false var onClearPicker = false + var geckoPrompt = GeckoDateTimePrompt("title", DATE, "", "", "") - val callback = object : TextCallback { - override fun dismiss() = Unit - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - override fun confirm(text: String?) { - confirmCalled = true - if (text!!.isEmpty()) { - onClearPicker = true - } - } - } val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { dateRequest = promptRequest } }) - promptDelegate.onDateTimePrompt(mock(), "title", DATETIME_TYPE_DATE, "", "", "", callback) + + var geckoResult = promptDelegate.onDateTimePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + confirmCalled = true + } + assertTrue(dateRequest is PromptRequest.TimeSelection) (dateRequest as PromptRequest.TimeSelection).onConfirm(Date()) assertTrue(confirmCalled) assertEquals((dateRequest as PromptRequest.TimeSelection).title, "title") + geckoPrompt = GeckoDateTimePrompt("title", DATE, "", "", "") + geckoResult = promptDelegate.onDateTimePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + onClearPicker = true + } + (dateRequest as PromptRequest.TimeSelection).onClear() assertTrue(onClearPicker) } @@ -271,31 +235,27 @@ class GeckoPromptDelegateTest { val mockSession = GeckoEngineSession(mock()) var timeSelectionRequest: PromptRequest.TimeSelection? = null var geckoDate: String? = null - val callback = object : TextCallback { - override fun dismiss() = Unit - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - override fun confirm(text: String?) { - geckoDate = text - } - } + + val geckoPrompt = + GeckoDateTimePrompt( + title = "title", + type = DATE, + defaultValue = "2019-11-29", + minValue = "2019-11-28", + maxValue = "2019-11-30" + ) val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { timeSelectionRequest = promptRequest as PromptRequest.TimeSelection } }) - promptDelegate.onDateTimePrompt( - mock(), - "title", - DATETIME_TYPE_DATE, - "2019-11-29", - "2019-11-28", - "2019-11-30", - callback - ) + + val geckoResult = promptDelegate.onDateTimePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + geckoDate = geckoPrompt.getGeckoResult()["datetime"].toString() + } + assertNotNull(timeSelectionRequest) with(timeSelectionRequest!!) { assertEquals(initialDate, "2019-11-29".toDate("yyyy-MM-dd")) @@ -313,23 +273,19 @@ class GeckoPromptDelegateTest { val mockSession = GeckoEngineSession(mock()) var dateRequest: PromptRequest? = null var confirmCalled = false - val callback = object : TextCallback { - override fun dismiss() = Unit - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - override fun confirm(text: String?) { - confirmCalled = true - } - } + val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { dateRequest = promptRequest } }) - promptDelegate.onDateTimePrompt(mock(), "title", DATETIME_TYPE_MONTH, "", "", "", callback) + val geckoPrompt = GeckoDateTimePrompt(type = MONTH) + + val geckoResult = promptDelegate.onDateTimePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + confirmCalled = true + } assertTrue(dateRequest is PromptRequest.TimeSelection) (dateRequest as PromptRequest.TimeSelection).onConfirm(Date()) assertTrue(confirmCalled) @@ -341,31 +297,25 @@ class GeckoPromptDelegateTest { val mockSession = GeckoEngineSession(mock()) var timeSelectionRequest: PromptRequest.TimeSelection? = null var geckoDate: String? = null - val callback = object : TextCallback { - override fun dismiss() = Unit - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - override fun confirm(text: String?) { - geckoDate = text - } - } + val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { timeSelectionRequest = promptRequest as PromptRequest.TimeSelection } }) - promptDelegate.onDateTimePrompt( - mock(), - "title", - DATETIME_TYPE_MONTH, - "2019-11", - "2019-11", - "2019-11", - callback + val geckoPrompt = GeckoDateTimePrompt( + title = "title", + type = MONTH, + defaultValue = "2019-11", + minValue = "2019-11", + maxValue = "2019-11" ) + val geckoResult = promptDelegate.onDateTimePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + geckoDate = geckoPrompt.getGeckoResult()["datetime"].toString() + } + assertNotNull(timeSelectionRequest) with(timeSelectionRequest!!) { assertEquals(initialDate, "2019-11".toDate("yyyy-MM")) @@ -383,23 +333,19 @@ class GeckoPromptDelegateTest { val mockSession = GeckoEngineSession(mock()) var dateRequest: PromptRequest? = null var confirmCalled = false - val callback = object : TextCallback { - override fun dismiss() = Unit - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - override fun confirm(text: String?) { - confirmCalled = true - } - } val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { dateRequest = promptRequest } }) - promptDelegate.onDateTimePrompt(mock(), "title", DATETIME_TYPE_WEEK, "", "", "", callback) + val geckoPrompt = GeckoDateTimePrompt(type = WEEK) + + val geckoResult = promptDelegate.onDateTimePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + confirmCalled = true + } + assertTrue(dateRequest is PromptRequest.TimeSelection) (dateRequest as PromptRequest.TimeSelection).onConfirm(Date()) assertTrue(confirmCalled) @@ -411,31 +357,25 @@ class GeckoPromptDelegateTest { val mockSession = GeckoEngineSession(mock()) var timeSelectionRequest: PromptRequest.TimeSelection? = null var geckoDate: String? = null - val callback = object : TextCallback { - override fun dismiss() = Unit - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - override fun confirm(text: String?) { - geckoDate = text - } - } val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { timeSelectionRequest = promptRequest as PromptRequest.TimeSelection } }) - promptDelegate.onDateTimePrompt( - mock(), - "title", - DATETIME_TYPE_WEEK, - "2018-W18", - "2018-W18", - "2018-W26", - callback + + val geckoPrompt = GeckoDateTimePrompt( + title = "title", + type = WEEK, + defaultValue = "2018-W18", + minValue = "2018-W18", + maxValue = "2018-W26" ) + val geckoResult = promptDelegate.onDateTimePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + geckoDate = geckoPrompt.getGeckoResult()["datetime"].toString() + } + assertNotNull(timeSelectionRequest) with(timeSelectionRequest!!) { assertEquals(initialDate, "2018-W18".toDate("yyyy-'W'ww")) @@ -454,23 +394,19 @@ class GeckoPromptDelegateTest { var dateRequest: PromptRequest? = null var confirmCalled = false - val callback = object : TextCallback { - override fun dismiss() = Unit - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - override fun confirm(text: String?) { - confirmCalled = true - } - } val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { dateRequest = promptRequest } }) - promptDelegate.onDateTimePrompt(mock(), "title", DATETIME_TYPE_TIME, "", "", "", callback) + val geckoPrompt = GeckoDateTimePrompt(type = TIME) + + val geckoResult = promptDelegate.onDateTimePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + confirmCalled = true + } + assertTrue(dateRequest is PromptRequest.TimeSelection) (dateRequest as PromptRequest.TimeSelection).onConfirm(Date()) assertTrue(confirmCalled) @@ -482,31 +418,26 @@ class GeckoPromptDelegateTest { val mockSession = GeckoEngineSession(mock()) var timeSelectionRequest: PromptRequest.TimeSelection? = null var geckoDate: String? = null - val callback = object : TextCallback { - override fun dismiss() = Unit - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - override fun confirm(text: String?) { - geckoDate = text - } - } + val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { timeSelectionRequest = promptRequest as PromptRequest.TimeSelection } }) - promptDelegate.onDateTimePrompt( - mock(), - "title", - DATETIME_TYPE_TIME, - "17:00", - "9:00", - "18:00", - callback + + val geckoPrompt = GeckoDateTimePrompt( + title = "title", + type = TIME, + defaultValue = "17:00", + minValue = "9:00", + maxValue = "18:00" ) + val geckoResult = promptDelegate.onDateTimePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + geckoDate = geckoPrompt.getGeckoResult()["datetime"].toString() + } + assertNotNull(timeSelectionRequest) with(timeSelectionRequest!!) { assertEquals(initialDate, "17:00".toDate("HH:mm")) @@ -525,26 +456,17 @@ class GeckoPromptDelegateTest { var dateRequest: PromptRequest? = null var confirmCalled = false - val callback = object : TextCallback { - override fun dismiss() = Unit - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - override fun confirm(text: String?) { - confirmCalled = true - } - } val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { dateRequest = promptRequest } }) - promptDelegate.onDateTimePrompt( - mock(), "title", - DATETIME_TYPE_DATETIME_LOCAL, "", "", "", callback - ) + val geckoResult = promptDelegate.onDateTimePrompt(mock(), GeckoDateTimePrompt(type = DATETIME_LOCAL)) + geckoResult!!.accept { + confirmCalled = true + } + assertTrue(dateRequest is PromptRequest.TimeSelection) (dateRequest as PromptRequest.TimeSelection).onConfirm(Date()) assertTrue(confirmCalled) @@ -556,31 +478,24 @@ class GeckoPromptDelegateTest { val mockSession = GeckoEngineSession(mock()) var timeSelectionRequest: PromptRequest.TimeSelection? = null var geckoDate: String? = null - val callback = object : TextCallback { - override fun dismiss() = Unit - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - override fun confirm(text: String?) { - geckoDate = text - } - } val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { timeSelectionRequest = promptRequest as PromptRequest.TimeSelection } }) - promptDelegate.onDateTimePrompt( - mock(), - "title", - DATETIME_TYPE_DATETIME_LOCAL, - "2018-06-12T19:30", - "2018-06-07T00:00", - "2018-06-14T00:00", - callback + val geckoPrompt = GeckoDateTimePrompt( + title = "title", + type = DATETIME_LOCAL, + defaultValue = "2018-06-12T19:30", + minValue = "2018-06-07T00:00", + maxValue = "2018-06-14T00:00" ) + val geckoResult = promptDelegate.onDateTimePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + geckoDate = geckoPrompt.getGeckoResult()["datetime"].toString() + } + assertNotNull(timeSelectionRequest) with(timeSelectionRequest!!) { assertEquals(initialDate, "2018-06-12T19:30".toDate("yyyy-MM-dd'T'HH:mm")) @@ -598,12 +513,12 @@ class GeckoPromptDelegateTest { val promptDelegate = GeckoPromptDelegate(mock()) promptDelegate.onDateTimePrompt( mock(), - "title", - 13223, - "17:00", - "9:00", - "18:00", - mock() + GeckoDateTimePrompt( + type = 13223, + defaultValue = "17:00", + minValue = "9:00", + maxValue = "18:00" + ) ) } @@ -623,10 +538,9 @@ class GeckoPromptDelegateTest { @Test fun `Calling onFilePrompt must provide a FilePicker PromptRequest`() { - val context = ApplicationProvider.getApplicationContext() + val context = testContext val mockSession = GeckoEngineSession(mock()) - var request: PromptRequest? = null var onSingleFileSelectedWasCalled = false var onMultipleFilesSelectedWasCalled = false var onDismissWasCalled = false @@ -635,115 +549,108 @@ class GeckoPromptDelegateTest { val shadowContentResolver = shadowOf(context.contentResolver) shadowContentResolver.registerInputStream(mockUri, mockFileInput) - - val callback = object : GeckoSession.PromptDelegate.FileCallback { - override fun dismiss() { - onDismissWasCalled = true - } - - override fun confirm(context: Context?, uri: Uri?) { - onSingleFileSelectedWasCalled = true - } - - override fun confirm(context: Context?, uris: Array?) { - onMultipleFilesSelectedWasCalled = true - } - - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - } + var filePickerRequest: PromptRequest.File = mock() val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { - request = promptRequest + filePickerRequest = promptRequest as PromptRequest.File } }) + var geckoPrompt = GeckoFilePrompt(type = GECKO_PROMPT_FILE_TYPE.SINGLE, capture = NONE) - promptDelegate.onFilePrompt(mock(), "title", GeckoSession.PromptDelegate.FILE_TYPE_SINGLE, emptyArray(), callback) - assertTrue(request is PromptRequest.File) - - val filePickerRequest = request as PromptRequest.File + var geckoResult = promptDelegate.onFilePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + onSingleFileSelectedWasCalled = true + } filePickerRequest.onSingleFileSelected(context, mockUri) assertTrue(onSingleFileSelectedWasCalled) + geckoPrompt = GeckoFilePrompt(type = GECKO_PROMPT_FILE_TYPE.MULTIPLE, capture = ANY) + geckoResult = promptDelegate.onFilePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + onMultipleFilesSelectedWasCalled = true + } + filePickerRequest.onMultipleFilesSelected(context, arrayOf(mockUri)) assertTrue(onMultipleFilesSelectedWasCalled) + geckoPrompt = GeckoFilePrompt(type = GECKO_PROMPT_FILE_TYPE.SINGLE, capture = NONE) + geckoResult = promptDelegate.onFilePrompt(mock(), geckoPrompt) + geckoResult!!.accept { + onDismissWasCalled = true + } + filePickerRequest.onDismiss() assertTrue(onDismissWasCalled) assertTrue(filePickerRequest.mimeTypes.isEmpty()) - Assert.assertFalse(filePickerRequest.isMultipleFilesSelection) + assertFalse(filePickerRequest.isMultipleFilesSelection) + assertEquals(PromptRequest.File.FacingMode.NONE, filePickerRequest.captureMode) promptDelegate.onFilePrompt( - mock(), "title", - GeckoSession.PromptDelegate.FILE_TYPE_MULTIPLE, emptyArray(), callback + mock(), + GeckoFilePrompt(type = GECKO_PROMPT_FILE_TYPE.MULTIPLE, capture = USER) ) - assertTrue((request as PromptRequest.File).isMultipleFilesSelection) + assertTrue(filePickerRequest.isMultipleFilesSelection) + assertEquals( + PromptRequest.File.FacingMode.FRONT_CAMERA, + filePickerRequest.captureMode + ) } @Test fun `Calling onAuthPrompt must provide an Authentication PromptRequest`() { val mockSession = GeckoEngineSession(mock()) - var request: PromptRequest? = null + var authRequest: PromptRequest.Authentication = mock() var onConfirmWasCalled = false var onConfirmOnlyPasswordWasCalled = false var onDismissWasCalled = false - val authOptions = mock() - - val geckoCallback = object : GeckoSession.PromptDelegate.AuthCallback { - - override fun confirm(username: String, password: String) { - onConfirmWasCalled = true - } - - override fun dismiss() { - onDismissWasCalled = true - } - - override fun confirm(password: String?) { - onConfirmOnlyPasswordWasCalled = true - } - - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - } val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { - request = promptRequest + authRequest = promptRequest as PromptRequest.Authentication } }) - promptDelegate.onAuthPrompt(mock(), "title", "message", authOptions, geckoCallback) - assertTrue(request is PromptRequest.Authentication) - - var authRequest = request as PromptRequest.Authentication + var geckoResult = + promptDelegate.onAuthPrompt(mock(), GeckoAuthPrompt(authOptions = mock())) + geckoResult!!.accept { + onConfirmWasCalled = true + } authRequest.onConfirm("", "") assertTrue(onConfirmWasCalled) + geckoResult = + promptDelegate.onAuthPrompt(mock(), GeckoAuthPrompt(authOptions = mock())) + geckoResult!!.accept { + onDismissWasCalled = true + } + authRequest.onDismiss() assertTrue(onDismissWasCalled) - authOptions.level = AUTH_LEVEL_SECURE - - authOptions.flags = authOptions.flags.or(AUTH_FLAG_ONLY_PASSWORD) - authOptions.flags = authOptions.flags.or(AUTH_FLAG_PREVIOUS_FAILED) - authOptions.flags = authOptions.flags.or(AUTH_FLAG_CROSS_ORIGIN_SUB_RESOURCE) - authOptions.flags = authOptions.flags.or(AUTH_FLAG_HOST) + val authOptions = GeckoAuthOptions() + ReflectionUtils.setField(authOptions, "level", GECKO_AUTH_LEVEL.SECURE) - promptDelegate.onAuthPrompt(mock(), "title", "message", authOptions, geckoCallback) + var flags = 0 + flags = flags.or(GECKO_AUTH_FLAGS.ONLY_PASSWORD) + flags = flags.or(GECKO_AUTH_FLAGS.PREVIOUS_FAILED) + flags = flags.or(GECKO_AUTH_FLAGS.CROSS_ORIGIN_SUB_RESOURCE) + flags = flags.or(GECKO_AUTH_FLAGS.HOST) + ReflectionUtils.setField(authOptions, "flags", flags) - authRequest = request as PromptRequest.Authentication + val geckoPrompt = GeckoAuthPrompt(authOptions = authOptions) + geckoResult = promptDelegate.onAuthPrompt(mock(), geckoPrompt) + geckoResult!!.accept { + val hasPassword = geckoPrompt.getGeckoResult().containsKey("password") + val hasUser = geckoPrompt.getGeckoResult().containsKey("username") + onConfirmOnlyPasswordWasCalled = hasPassword && hasUser == false + } authRequest.onConfirm("", "") @@ -752,60 +659,43 @@ class GeckoPromptDelegateTest { assertTrue(previousFailed) assertTrue(isCrossOrigin) - assertEquals(method, HOST) - assertEquals(level, SECURED) + assertEquals(method, AC_AUTH_METHOD.HOST) + assertEquals(level, AC_AUTH_LEVEL.SECURED) assertTrue(onConfirmOnlyPasswordWasCalled) } - authOptions.level = AUTH_LEVEL_PW_ENCRYPTED + ReflectionUtils.setField(authOptions, "level", GECKO_AUTH_LEVEL.PW_ENCRYPTED) - promptDelegate.onAuthPrompt(mock(), "title", "message", authOptions, geckoCallback) - authRequest = request as PromptRequest.Authentication + promptDelegate.onAuthPrompt(mock(), GeckoAuthPrompt(authOptions = authOptions)) - assertEquals(authRequest.level, PASSWORD_ENCRYPTED) + assertEquals(authRequest.level, AC_AUTH_LEVEL.PASSWORD_ENCRYPTED) - authOptions.level = -2423 + ReflectionUtils.setField(authOptions, "level", -2423) - promptDelegate.onAuthPrompt(mock(), "title", "message", authOptions, geckoCallback) - authRequest = request as PromptRequest.Authentication + promptDelegate.onAuthPrompt(mock(), GeckoAuthPrompt(authOptions = authOptions)) - assertEquals(authRequest.level, NONE) + assertEquals(authRequest.level, AC_AUTH_LEVEL.NONE) } @Test fun `Calling onColorPrompt must provide a Color PromptRequest`() { val mockSession = GeckoEngineSession(mock()) - var request: PromptRequest? = null + var colorRequest: PromptRequest.Color = mock() var onConfirmWasCalled = false var onDismissWasCalled = false - val geckoCallback = object : TextCallback { - - override fun confirm(text: String?) { - onConfirmWasCalled = true - } - - override fun dismiss() { - onDismissWasCalled = true - } - - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - } - val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { - request = promptRequest + colorRequest = promptRequest as PromptRequest.Color } }) - promptDelegate.onColorPrompt(mock(), "title", "#e66465", geckoCallback) - assertTrue(request is PromptRequest.Color) - - var colorRequest = request as PromptRequest.Color + var geckoResult = + promptDelegate.onColorPrompt(mock(), GeckoColorPrompt(defaultValue = "#e66465")) + geckoResult!!.accept { + onConfirmWasCalled = true + } with(colorRequest) { @@ -813,68 +703,57 @@ class GeckoPromptDelegateTest { onConfirm("#f6b73c") assertTrue(onConfirmWasCalled) + } - onDismiss() - assertTrue(onDismissWasCalled) + geckoResult = promptDelegate.onColorPrompt(mock(), GeckoColorPrompt()) + geckoResult!!.accept { + onDismissWasCalled = true } - promptDelegate.onColorPrompt(mock(), null, null, geckoCallback) - colorRequest = request as PromptRequest.Color + colorRequest.onDismiss() + assertTrue(onDismissWasCalled) with(colorRequest) { - assertEquals(defaultColor, "") + assertEquals(defaultColor, "defaultValue") } } @Test fun `onTextPrompt must provide an TextPrompt PromptRequest`() { val mockSession = GeckoEngineSession(mock()) - var request: PromptRequest? = null + var request: PromptRequest.TextPrompt = mock() var dismissWasCalled = false var confirmWasCalled = false - var setCheckboxValueWasCalled = false - - val callback = object : TextCallback { - - override fun confirm(text: String?) { - confirmWasCalled = true - } - - override fun setCheckboxValue(value: Boolean) { - setCheckboxValueWasCalled = true - } - - override fun dismiss() { - dismissWasCalled = true - } - - override fun getCheckboxValue(): Boolean = false - override fun hasCheckbox(): Boolean = false - override fun getCheckboxMessage(): String = "" - } val promptDelegate = GeckoPromptDelegate(mockSession) mockSession.register(object : EngineSession.Observer { override fun onPromptRequest(promptRequest: PromptRequest) { - request = promptRequest + request = promptRequest as PromptRequest.TextPrompt } }) - promptDelegate.onTextPrompt(mock(), "title", "label", "value", callback) + var geckoResult = promptDelegate.onTextPrompt(mock(), GeckoTextPrompt()) + geckoResult!!.accept { + dismissWasCalled = true + } - with(request as PromptRequest.TextPrompt) { + with(request) { assertEquals(title, "title") - assertEquals(inputLabel, "label") - assertEquals(inputValue, "value") + assertEquals(inputLabel, "message") + assertEquals(inputValue, "defaultValue") onDismiss() assertTrue(dismissWasCalled) + } - onConfirm(true, "newInput") - assertTrue(setCheckboxValueWasCalled) - assertTrue(confirmWasCalled) + geckoResult = promptDelegate.onTextPrompt(mock(), GeckoTextPrompt()) + geckoResult!!.accept { + confirmWasCalled = true } + + request.onConfirm(true, "newInput") + assertTrue(confirmWasCalled) } @Test @@ -892,18 +771,12 @@ class GeckoPromptDelegateTest { } }) - var geckoCallback = promptDelegate.onPopupRequest(mock(), "www.popuptest.com/") - - val geckoThen: (AllowOrDeny?) -> GeckoResult = { - when (it!!) { - AllowOrDeny.ALLOW -> { onAllowWasCalled = true } - AllowOrDeny.DENY -> { onDenyWasCalled = true } - } - geckoCallback + var geckoPrompt = GeckoPopupPrompt(targetUri = "www.popuptest.com/") + var geckoResult = promptDelegate.onPopupPrompt(mock(), geckoPrompt) + geckoResult.accept { + onAllowWasCalled = geckoPrompt.getGeckoResult()["response"] == true } - geckoCallback.then(geckoThen) - with(request!!) { assertEquals(targetUri, "www.popuptest.com/") @@ -911,8 +784,11 @@ class GeckoPromptDelegateTest { assertTrue(onAllowWasCalled) } - geckoCallback = promptDelegate.onPopupRequest(mock(), "www.popuptest.com/") - geckoCallback.then(geckoThen) + geckoPrompt = GeckoPopupPrompt() + geckoResult = promptDelegate.onPopupPrompt(mock(), geckoPrompt) + geckoResult.accept { + onDenyWasCalled = geckoPrompt.getGeckoResult()["response"] == false + } request!!.onDeny() assertTrue(onDenyWasCalled) @@ -921,12 +797,11 @@ class GeckoPromptDelegateTest { @Test fun `onButtonPrompt must provide a Confirm PromptRequest`() { val mockSession = GeckoEngineSession(mock()) - var request: PromptRequest.Confirm? = null + var request: PromptRequest.Confirm = mock() var onPositiveButtonWasCalled = false var onNegativeButtonWasCalled = false var onNeutralButtonWasCalled = false var dismissWasCalled = false - var setCheckboxValueWasCalled = false val promptDelegate = GeckoPromptDelegate(mockSession) @@ -936,72 +811,103 @@ class GeckoPromptDelegateTest { } }) - val callback = object : GeckoSession.PromptDelegate.ButtonCallback { - - override fun confirm(button: Int) { - when (button) { - BUTTON_TYPE_POSITIVE -> onPositiveButtonWasCalled = true - BUTTON_TYPE_NEGATIVE -> onNegativeButtonWasCalled = true - BUTTON_TYPE_NEUTRAL -> onNeutralButtonWasCalled = true - } - } - - override fun setCheckboxValue(value: Boolean) { - setCheckboxValueWasCalled = true - } - - override fun dismiss() { - dismissWasCalled = true - } - - override fun getCheckboxValue(): Boolean = false - override fun hasCheckbox(): Boolean = true - override fun getCheckboxMessage(): String = "" + var geckoResult = promptDelegate.onButtonPrompt(mock(), GeckoPromptPrompt()) + geckoResult!!.accept { + onPositiveButtonWasCalled = true } - promptDelegate.onButtonPrompt( - mock(), - "title", - "message", - arrayOf("positive", "neutral", "negative"), - callback - ) - - with(request!!) { + with(request) { assertNotNull(request) assertEquals(title, "title") assertEquals(message, "message") - assertEquals(hasShownManyDialogs, true) - assertEquals(positiveButtonTitle, "positive") - assertEquals(negativeButtonTitle, "negative") - assertEquals(neutralButtonTitle, "neutral") onConfirmPositiveButton(false) assertTrue(onPositiveButtonWasCalled) + } + + geckoResult = promptDelegate.onButtonPrompt(mock(), GeckoPromptPrompt()) + geckoResult!!.accept { + onNeutralButtonWasCalled = true + } - onConfirmNegativeButton(false) - assertTrue(onNegativeButtonWasCalled) + request.onConfirmNeutralButton(false) + assertTrue(onNeutralButtonWasCalled) - onConfirmNeutralButton(false) - assertTrue(onNeutralButtonWasCalled) + geckoResult = promptDelegate.onButtonPrompt(mock(), GeckoPromptPrompt()) + geckoResult!!.accept { + onNegativeButtonWasCalled = true + } - assertTrue(setCheckboxValueWasCalled) + request.onConfirmNegativeButton(false) + assertTrue(onNegativeButtonWasCalled) - onDismiss() - assertTrue(dismissWasCalled) + geckoResult = promptDelegate.onButtonPrompt(mock(), GeckoPromptPrompt()) + geckoResult!!.accept { + dismissWasCalled = true } + + request.onDismiss() + assertTrue(dismissWasCalled) } - open class DefaultGeckoChoiceCallback : GeckoSession.PromptDelegate.ChoiceCallback { - override fun confirm(items: Array?) = Unit - override fun dismiss() {} - override fun getCheckboxValue() = false - override fun setCheckboxValue(value: Boolean) = Unit - override fun hasCheckbox() = false - override fun getCheckboxMessage() = "" - override fun confirm(ids: Array) = Unit - override fun confirm(item: GeckoChoice) = Unit - override fun confirm(id: String?) = Unit + class GeckoChoicePrompt( + title: String, + message: String, + type: Int, + choices: Array + ) : GeckoSession.PromptDelegate.ChoicePrompt(title, message, type, choices) + + class GeckoAlertPrompt(title: String = "title", message: String = "message") : + GeckoSession.PromptDelegate.AlertPrompt(title, message) + + class GeckoDateTimePrompt( + title: String = "title", + type: Int, + defaultValue: String = "", + minValue: String = "", + maxValue: String = "" + ) : GeckoSession.PromptDelegate.DateTimePrompt(title, type, defaultValue, minValue, maxValue) + + class GeckoFilePrompt( + title: String = "title", + type: Int, + capture: Int = 0, + mimeTypes: Array = emptyArray() + ) : GeckoSession.PromptDelegate.FilePrompt(title, type, capture, mimeTypes) + + class GeckoAuthPrompt( + title: String = "title", + message: String = "message", + authOptions: AuthOptions + ) : GeckoSession.PromptDelegate.AuthPrompt(title, message, authOptions) + + class GeckoColorPrompt( + title: String = "title", + defaultValue: String = "defaultValue" + ) : GeckoSession.PromptDelegate.ColorPrompt(title, defaultValue) + + class GeckoTextPrompt( + title: String = "title", + message: String = "message", + defaultValue: String = "defaultValue" + ) : GeckoSession.PromptDelegate.TextPrompt(title, message, defaultValue) + + class GeckoPopupPrompt( + targetUri: String = "targetUri" + ) : GeckoSession.PromptDelegate.PopupPrompt(targetUri) + + class GeckoPromptPrompt( + title: String = "title", + message: String = "message" + ) : GeckoSession.PromptDelegate.ButtonPrompt(title, message) + + class GeckoAuthOptions : GeckoSession.PromptDelegate.AuthPrompt.AuthOptions() + + private fun GeckoSession.PromptDelegate.BasePrompt.getGeckoResult(): GeckoBundle { + val javaClass = GeckoSession.PromptDelegate.BasePrompt::class.java + val method = javaClass.getDeclaredMethod("ensureResult") + method.isAccessible = true + return (method.invoke(this) as GeckoBundle) } -} \ No newline at end of file +} diff --git a/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/window/GeckoWindowRequestTest.kt b/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/window/GeckoWindowRequestTest.kt new file mode 100644 index 00000000000..e7fb81da1e4 --- /dev/null +++ b/components/browser/engine-gecko-beta/src/test/java/mozilla/components/browser/engine/gecko/window/GeckoWindowRequestTest.kt @@ -0,0 +1,23 @@ +/* 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 mozilla.components.browser.engine.gecko.window + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.browser.engine.gecko.GeckoEngineSession +import mozilla.components.support.test.mock +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class GeckoWindowRequestTest { + + @Test + fun testPrepare() { + val engineSession: GeckoEngineSession = mock() + val windowRequest = GeckoWindowRequest("mozilla.org", engineSession) + assertEquals(engineSession, windowRequest.prepare()) + } +} \ No newline at end of file diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt index 3a8d674563b..1542c0db5e3 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt @@ -12,8 +12,8 @@ import mozilla.components.browser.engine.gecko.mediaquery.toGeckoValue import mozilla.components.browser.engine.gecko.webextension.GeckoWebExtension import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.EngineSession -import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy import mozilla.components.concept.engine.EngineSession.SafeBrowsingPolicy +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy import mozilla.components.concept.engine.EngineSessionState import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.Settings diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt index 763e09503a9..978fed2c50c 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt @@ -8,7 +8,6 @@ import android.annotation.SuppressLint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import mozilla.components.browser.engine.gecko.media.GeckoMediaDelegate import mozilla.components.browser.engine.gecko.permission.GeckoPermissionRequest @@ -18,15 +17,18 @@ import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineSessionState import mozilla.components.concept.engine.HitResult import mozilla.components.concept.engine.Settings +import mozilla.components.concept.engine.content.blocking.Tracker import mozilla.components.concept.engine.history.HistoryTrackingDelegate +import mozilla.components.concept.engine.manifest.WebAppManifestParser import mozilla.components.concept.engine.request.RequestInterceptor -import mozilla.components.concept.engine.content.blocking.Tracker import mozilla.components.concept.engine.request.RequestInterceptor.InterceptionResponse import mozilla.components.concept.storage.VisitType import mozilla.components.support.ktx.android.util.Base64 import mozilla.components.support.ktx.kotlin.isEmail import mozilla.components.support.ktx.kotlin.isGeoLocation import mozilla.components.support.ktx.kotlin.isPhone +import mozilla.components.support.utils.DownloadUtils +import org.json.JSONObject import org.mozilla.geckoview.AllowOrDeny import org.mozilla.geckoview.ContentBlocking import org.mozilla.geckoview.GeckoResult @@ -321,8 +323,9 @@ class GeckoEngineSession( GeckoResult.fromValue(AllowOrDeny.DENY) } else { notifyObservers { - // As the name LoadRequest.isRedirect may imply this flag is about http redirects. The flag + // Unlike the name LoadRequest.isRedirect may imply this flag is not about http redirects. The flag // is "True if and only if the request was triggered by an HTTP redirect." + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1545170 onLoadRequest( url = request.uri, triggeredByRedirect = request.isRedirect, @@ -450,12 +453,10 @@ class GeckoEngineSession( return GeckoResult.fromValue(false) } - val result = GeckoResult() - launch { + return launchGeckoResult { delegate.onVisited(url, visitType) - result.complete(true) + true } - return result } override fun getVisited( @@ -468,12 +469,10 @@ class GeckoEngineSession( val delegate = settings.historyTrackingDelegate ?: return GeckoResult.fromValue(null) - val result = GeckoResult() - launch { - val visits: List? = delegate.getVisited(urls.toList()) - result.complete(visits?.toBooleanArray()) + return launchGeckoResult { + val visits = delegate.getVisited(urls.toList()) + visits.toBooleanArray() } - return result } } @@ -496,23 +495,48 @@ class GeckoEngineSession( override fun onCrash(session: GeckoSession) { stateBeforeCrash = lastSessionState - geckoSession.close() - createGeckoSession() + recoverGeckoSession() notifyObservers { onCrash() } } + override fun onKill(session: GeckoSession) { + // The content process of this session got killed (resources reclaimed by Android). + // Let's recover and restore the last known state. + + val state = lastSessionState + + recoverGeckoSession() + + state?.let { geckoSession.restoreState(it) } + + notifyObservers { onProcessKilled() } + } + + private fun recoverGeckoSession() { + // Recover the GeckoSession after the process getting killed or crashing. We create a + // new underlying GeckoSession. + // Eventually we may be able to re-use the same GeckoSession by re-opening it. However + // that seems to have caused issues: + // https://github.com/mozilla-mobile/android-components/issues/3640 + + geckoSession.close() + createGeckoSession() + } + override fun onFullScreen(session: GeckoSession, fullScreen: Boolean) { notifyObservers { onFullScreenChange(fullScreen) } } override fun onExternalResponse(session: GeckoSession, response: GeckoSession.WebResponseInfo) { notifyObservers { + val fileName = response.filename + ?: DownloadUtils.guessFileName(null, response.uri, response.contentType) onExternalResource( url = response.uri, contentLength = response.contentLength, contentType = response.contentType, - fileName = response.filename) + fileName = fileName) } } @@ -532,6 +556,13 @@ class GeckoEngineSession( } override fun onFocusRequest(session: GeckoSession) = Unit + + override fun onWebAppManifest(session: GeckoSession, manifest: JSONObject) { + val parsed = WebAppManifestParser().parse(manifest) + if (parsed is WebAppManifestParser.Result.Success) { + notifyObservers { onWebAppManifestLoaded(parsed.manifest) } + } + } } private fun createContentBlockingDelegate() = object : ContentBlocking.Delegate { diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineView.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineView.kt index 46b9cfe3f9c..915e1e52e07 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineView.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineView.kt @@ -44,6 +44,11 @@ class GeckoEngineView @JvmOverloads constructor( override fun onCrash() { rebind() } + + override fun onProcessKilled() { + rebind() + } + override fun onAppPermissionRequest(permissionRequest: PermissionRequest) = Unit override fun onContentPermissionRequest(permissionRequest: PermissionRequest) = Unit } diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoResult.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoResult.kt new file mode 100644 index 00000000000..687eb6ba79d --- /dev/null +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoResult.kt @@ -0,0 +1,47 @@ +/* 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 mozilla.components.browser.engine.gecko + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.launch +import org.mozilla.geckoview.GeckoResult +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +/** + * Wait for a GeckoResult to be complete in a co-routine. + */ +suspend fun GeckoResult.await() = suspendCoroutine { continuation -> + then({ + continuation.resume(it) + GeckoResult() + }, { + continuation.resumeWithException(it) + GeckoResult() + }) +} + +/** + * Create a GeckoResult from a co-routine. + */ +@Suppress("TooGenericExceptionCaught") +fun CoroutineScope.launchGeckoResult( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> T +) = GeckoResult().apply { + launch(context, start) { + try { + val value = block() + complete(value) + } catch (exception: Throwable) { + completeExceptionally(exception) + } + } +} diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/fetch/GeckoViewFetchClient.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/fetch/GeckoViewFetchClient.kt index b8967eb9535..6a699bc4019 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/fetch/GeckoViewFetchClient.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/fetch/GeckoViewFetchClient.kt @@ -38,23 +38,15 @@ class GeckoViewFetchClient( internal var executor: GeckoWebExecutor = GeckoWebExecutor(runtime) @Throws(IOException::class) - @Suppress("ComplexMethod") override fun fetch(request: Request): Response { - val webRequest = with(request) { - WebRequest.Builder(url) - .method(method.name) - .addHeadersFrom(request, defaultHeaders) - .addBodyFrom(request) - .cacheMode(if (request.useCaches) CACHE_MODE_DEFAULT else CACHE_MODE_RELOAD) - .build() - } + val webRequest = request.toWebRequest(defaultHeaders) val readTimeOut = request.readTimeout ?: maxReadTimeOut val readTimeOutMillis = readTimeOut.let { (timeout, unit) -> unit.toMillis(timeout) } - try { + return try { var fetchFlags = 0 if (request.cookiePolicy == Request.CookiePolicy.OMIT) { fetchFlags += GeckoWebExecutor.FETCH_FLAGS_ANONYMOUS @@ -63,7 +55,7 @@ class GeckoViewFetchClient( fetchFlags += GeckoWebExecutor.FETCH_FLAGS_NO_REDIRECTS } val webResponse = executor.fetch(webRequest, fetchFlags).poll(readTimeOutMillis) - return webResponse?.toResponse() ?: throw IOException("Fetch failed with null response") + webResponse?.toResponse() ?: throw IOException("Fetch failed with null response") } catch (e: TimeoutException) { throw SocketTimeoutException() } catch (e: WebRequestError) { @@ -76,6 +68,13 @@ class GeckoViewFetchClient( } } +private fun Request.toWebRequest(defaultHeaders: Headers): WebRequest = WebRequest.Builder(url) + .method(method.name) + .addHeadersFrom(this, defaultHeaders) + .addBodyFrom(this) + .cacheMode(if (useCaches) CACHE_MODE_DEFAULT else CACHE_MODE_RELOAD) + .build() + private fun WebRequest.Builder.addHeadersFrom(request: Request, defaultHeaders: Headers): WebRequest.Builder { defaultHeaders.filter { header -> request.headers?.contains(header.name) != true diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/media/GeckoMedia.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/media/GeckoMedia.kt index c3347d1a733..2c9e77415d8 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/media/GeckoMedia.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/media/GeckoMedia.kt @@ -49,7 +49,7 @@ internal class GeckoMedia( private class MediaDelegate( private val media: GeckoMedia ) : MediaElement.Delegate { - @Suppress("ComplexMethod") + override fun onPlaybackStateChange(mediaElement: MediaElement, mediaState: Int) { when (mediaState) { MEDIA_STATE_PLAY -> media.playbackState = Media.PlaybackState.PLAY diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/media/GeckoMediaDelegate.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/media/GeckoMediaDelegate.kt index b8337b47f7d..df8bc42f1e8 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/media/GeckoMediaDelegate.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/media/GeckoMediaDelegate.kt @@ -22,13 +22,11 @@ internal class GeckoMediaDelegate( private val mediaMap: MutableMap = mutableMapOf() override fun onMediaAdd(session: GeckoSession, element: MediaElement) { - engineSession.notifyObservers { - val media = GeckoMedia(element) - - mediaMap[element] = media - - onMediaAdded(media) + val media = GeckoMedia(element).also { + mediaMap[element] = it } + + engineSession.notifyObservers { onMediaAdded(media) } } override fun onMediaRemove(session: GeckoSession, element: MediaElement) { diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/permission/GeckoPermissionRequest.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/permission/GeckoPermissionRequest.kt index aabef4223fc..4744cb91845 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/permission/GeckoPermissionRequest.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/permission/GeckoPermissionRequest.kt @@ -4,24 +4,21 @@ package mozilla.components.browser.engine.gecko.permission +import android.Manifest.permission.ACCESS_COARSE_LOCATION +import android.Manifest.permission.ACCESS_FINE_LOCATION +import android.Manifest.permission.CAMERA +import android.Manifest.permission.RECORD_AUDIO import mozilla.components.concept.engine.permission.Permission import mozilla.components.concept.engine.permission.PermissionRequest import org.mozilla.geckoview.GeckoSession.PermissionDelegate -import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_GEOLOCATION -import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource.SOURCE_AUDIOCAPTURE -import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource.SOURCE_APPLICATION -import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource.SOURCE_BROWSER import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource.SOURCE_CAMERA import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource.SOURCE_MICROPHONE import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource.SOURCE_OTHER import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource.SOURCE_SCREEN -import org.mozilla.geckoview.GeckoSession.PermissionDelegate.MediaSource.SOURCE_WINDOW -import android.Manifest.permission.ACCESS_COARSE_LOCATION -import android.Manifest.permission.ACCESS_FINE_LOCATION -import android.Manifest.permission.CAMERA -import android.Manifest.permission.RECORD_AUDIO +import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION +import org.mozilla.geckoview.GeckoSession.PermissionDelegate.PERMISSION_GEOLOCATION /** * Gecko-based implementation of [PermissionRequest]. @@ -118,26 +115,27 @@ sealed class GeckoPermissionRequest constructor( } companion object { - @Suppress("ComplexMethod", "SwitchIntDef") - fun mapPermission(mediaSource: MediaSource): Permission { + fun mapPermission(mediaSource: MediaSource): Permission = if (mediaSource.type == MediaSource.TYPE_AUDIO) { - return when (mediaSource.source) { - SOURCE_AUDIOCAPTURE -> Permission.ContentAudioCapture(mediaSource.id, mediaSource.name) - SOURCE_MICROPHONE -> Permission.ContentAudioMicrophone(mediaSource.id, mediaSource.name) - SOURCE_OTHER -> Permission.ContentAudioOther(mediaSource.id, mediaSource.name) - else -> Permission.Generic(mediaSource.id, mediaSource.name) - } + mapAudioPermission(mediaSource) } else { - return when (mediaSource.source) { - SOURCE_CAMERA -> Permission.ContentVideoCamera(mediaSource.id, mediaSource.name) - SOURCE_APPLICATION -> Permission.ContentVideoApplication(mediaSource.id, mediaSource.name) - SOURCE_BROWSER -> Permission.ContentVideoBrowser(mediaSource.id, mediaSource.name) - SOURCE_SCREEN -> Permission.ContentVideoScreen(mediaSource.id, mediaSource.name) - SOURCE_WINDOW -> Permission.ContentVideoWindow(mediaSource.id, mediaSource.name) - SOURCE_OTHER -> Permission.ContentVideoOther(mediaSource.id, mediaSource.name) - else -> Permission.Generic(mediaSource.id, mediaSource.name) - } + mapVideoPermission(mediaSource) } + + @Suppress("SwitchIntDef") + private fun mapAudioPermission(mediaSource: MediaSource) = when (mediaSource.source) { + SOURCE_AUDIOCAPTURE -> Permission.ContentAudioCapture(mediaSource.id, mediaSource.name) + SOURCE_MICROPHONE -> Permission.ContentAudioMicrophone(mediaSource.id, mediaSource.name) + SOURCE_OTHER -> Permission.ContentAudioOther(mediaSource.id, mediaSource.name) + else -> Permission.Generic(mediaSource.id, mediaSource.name) + } + + @Suppress("ComplexMethod", "SwitchIntDef") + private fun mapVideoPermission(mediaSource: MediaSource) = when (mediaSource.source) { + SOURCE_CAMERA -> Permission.ContentVideoCamera(mediaSource.id, mediaSource.name) + SOURCE_SCREEN -> Permission.ContentVideoScreen(mediaSource.id, mediaSource.name) + SOURCE_OTHER -> Permission.ContentVideoOther(mediaSource.id, mediaSource.name) + else -> Permission.Generic(mediaSource.id, mediaSource.name) } } } diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt index ce03a3da5d5..3297fae4792 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegate.kt @@ -132,6 +132,8 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe val isMultipleFilesSelection = selectionType == GeckoSession.PromptDelegate.FILE_TYPE_MULTIPLE + val captureMode = PromptRequest.File.FacingMode.NONE + val onSelectSingle: (Context, Uri) -> Unit = { context, uri -> callback.confirm(context, uri.toFileUri(context)) } @@ -145,6 +147,7 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe PromptRequest.File( mimeTypes ?: emptyArray(), isMultipleFilesSelection, + captureMode, onSelectSingle, onSelectMultiple, onDismiss diff --git a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/webextension/GeckoWebExtension.kt b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/webextension/GeckoWebExtension.kt index bdbb86dcfd2..d7408b45a1c 100644 --- a/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/webextension/GeckoWebExtension.kt +++ b/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/webextension/GeckoWebExtension.kt @@ -84,7 +84,7 @@ class GeckoWebExtension( } val geckoSession = (session as GeckoEngineSession).geckoSession - geckoSession.setMessageDelegate(messageDelegate, name) + geckoSession.setMessageDelegate(nativeExtension, messageDelegate, name) } /** @@ -92,7 +92,7 @@ class GeckoWebExtension( */ override fun hasContentMessageHandler(session: EngineSession, name: String): Boolean { val geckoSession = (session as GeckoEngineSession).geckoSession - return geckoSession.getMessageDelegate(name) != null + return geckoSession.getMessageDelegate(nativeExtension, name) != null } } diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt index 317cff2d3e6..87e30497433 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt @@ -20,6 +20,7 @@ import mozilla.components.concept.engine.HitResult import mozilla.components.concept.engine.UnsupportedSettingException import mozilla.components.concept.engine.content.blocking.Tracker import mozilla.components.concept.engine.history.HistoryTrackingDelegate +import mozilla.components.concept.engine.manifest.WebAppManifest import mozilla.components.concept.engine.permission.PermissionRequest import mozilla.components.concept.engine.request.RequestInterceptor import mozilla.components.concept.storage.VisitType @@ -30,6 +31,7 @@ import mozilla.components.support.test.mock import mozilla.components.support.test.whenever import mozilla.components.support.utils.ThreadUtils import mozilla.components.test.ReflectionUtils +import org.json.JSONObject import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull @@ -182,8 +184,8 @@ class GeckoEngineSessionTest { geckoSessionProvider = geckoSessionProvider) var observedUrl = "" - var observedCanGoBack: Boolean = false - var observedCanGoForward: Boolean = false + var observedCanGoBack = false + var observedCanGoForward = false engineSession.register(object : EngineSession.Observer { override fun onLocationChange(url: String) { observedUrl = url } override fun onNavigationStateChange(canGoBack: Boolean?, canGoForward: Boolean?) { @@ -231,6 +233,29 @@ class GeckoEngineSessionTest { cookie = null) } + @Test + fun contentDelegateNotifiesObserverAboutWebAppManifest() { + val engineSession = GeckoEngineSession(mock(), + geckoSessionProvider = geckoSessionProvider) + + val observer: EngineSession.Observer = mock() + engineSession.register(observer) + + val json = JSONObject().apply { + put("name", "Minimal") + put("start_url", "/") + } + val manifest = WebAppManifest( + name = "Minimal", + startUrl = "/" + ) + + captureDelegates() + contentDelegate.value.onWebAppManifest(mock(), json) + + verify(observer).onWebAppManifestLoaded(manifest) + } + @Test fun permissionDelegateNotifiesObservers() { val engineSession = GeckoEngineSession(mock(), @@ -543,7 +568,7 @@ class GeckoEngineSessionTest { // Nothing breaks if history delegate isn't configured. historyDelegate.value.onVisited(geckoSession, "https://www.mozilla.com", null, GeckoSession.HistoryDelegate.VISIT_TOP_LEVEL) - engineSession.job.children.forEach { it.join() } + engineSession.job.children.forEach { it.join() } engineSession.settings.historyTrackingDelegate = historyTrackingDelegate @@ -780,7 +805,9 @@ class GeckoEngineSessionTest { geckoCatgories = geckoCatgories.or(ContentBlocking.AT_CONTENT) geckoCatgories = geckoCatgories.or(ContentBlocking.AT_TEST) - contentBlockingDelegate.value.onContentBlocked(geckoSession, ContentBlocking.BlockEvent("tracker1", geckoCatgories)) + contentBlockingDelegate.value.onContentBlocked(geckoSession, + ContentBlocking.BlockEvent("tracker1", geckoCatgories) + ) assertEquals("tracker1", trackerBlocked!!.url) val expectedBlockedCategories = listOf( @@ -817,13 +844,12 @@ class GeckoEngineSessionTest { }) val allPolicy = TrackingProtectionPolicy.select( - arrayOf(TrackingCategory.AD) - ) + arrayOf(TrackingCategory.AD)) val regularOnlyPolicy = TrackingProtectionPolicy.select( - arrayOf(TrackingCategory.AD) + trackingCategories = arrayOf(TrackingCategory.AD) ).forRegularSessionsOnly() val privateOnlyPolicy = TrackingProtectionPolicy.select( - arrayOf(TrackingCategory.AD) + trackingCategories = arrayOf(TrackingCategory.AD) ).forPrivateSessionsOnly() session.enableTrackingProtection(allPolicy) @@ -1601,7 +1627,7 @@ class GeckoEngineSessionTest { captureDelegates() - var crashedState: Boolean = false + var crashedState = false engineSession.register(object : EngineSession.Observer { override fun onCrash() { @@ -1667,11 +1693,13 @@ class GeckoEngineSessionTest { captureDelegates() + var observedUrl: String? = null var observedTriggeredByRedirect: Boolean? = null engineSession.register(object : EngineSession.Observer { override fun onLoadRequest(url: String, triggeredByRedirect: Boolean, triggeredByWebContent: Boolean) { observedTriggeredByRedirect = triggeredByRedirect + observedUrl = url } }) @@ -1680,11 +1708,13 @@ class GeckoEngineSessionTest { assertNotNull(observedTriggeredByRedirect) assertTrue(observedTriggeredByRedirect!!) + assertEquals("sample:about", observedUrl) navigationDelegate.value.onLoadRequest( mock(), mockLoadRequest("sample:about", triggeredByRedirect = false)) assertFalse(observedTriggeredByRedirect!!) + assertEquals("sample:about", observedUrl) } @Test @@ -1781,10 +1811,11 @@ class GeckoEngineSessionTest { val observer: EngineSession.Observer = mock() engineSession.register(observer) + val fakeUri = "sample:about" navigationDelegate.value.onLoadRequest( - mock(), mockLoadRequest("sample:about", triggeredByRedirect = true)) + mock(), mockLoadRequest(fakeUri, triggeredByRedirect = true)) - verify(observer, never()).onLoadRequest(eq("sample:about"), anyBoolean(), anyBoolean()) + verify(observer, never()).onLoadRequest(eq(fakeUri), anyBoolean(), anyBoolean()) } @Test @@ -1796,6 +1827,32 @@ class GeckoEngineSessionTest { assertEquals(LoadUrlFlags.BYPASS_CLASSIFIER, GeckoSession.LOAD_FLAGS_BYPASS_CLASSIFIER) } + @Test + fun `onKill will recover, restore state and notify observers`() { + val engineSession = GeckoEngineSession(mock(), + geckoSessionProvider = geckoSessionProvider) + + captureDelegates() + + var observerNotified = false + + engineSession.register(object : EngineSession.Observer { + override fun onProcessKilled() { + observerNotified = true + } + }) + + val mockedState: GeckoSession.SessionState = mock() + progressDelegate.value.onSessionStateChange(geckoSession, mockedState) + + verify(geckoSession, never()).restoreState(mockedState) + + contentDelegate.value.onKill(geckoSession) + + verify(geckoSession).restoreState(mockedState) + assertTrue(observerNotified) + } + private fun mockGeckoSession(): GeckoSession { val session = mock() whenever(session.settings).thenReturn( diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt index b1070bed722..f9e2ee7a8f2 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt @@ -140,8 +140,8 @@ class GeckoEngineTest { val trackingStrictCategories = TrackingProtectionPolicy.strict().trackingCategories.sumBy { it.id } assertEquals(trackingStrictCategories, ContentBlocking.AT_STRICT) - assertTrue(contentBlockingSettings.contains(SafeBrowsingPolicy.RECOMMENDED)) - + val safeStrictBrowsingCategories = SafeBrowsingPolicy.RECOMMENDED.id + assertEquals(safeStrictBrowsingCategories, ContentBlocking.SB_ALL) assertEquals(contentBlockingSettings.cookieBehavior, CookiePolicy.ACCEPT_NON_TRACKERS.id) engine.settings.safeBrowsingPolicy = arrayOf(SafeBrowsingPolicy.PHISHING) @@ -214,14 +214,11 @@ class GeckoEngineTest { cookiePolicy = CookiePolicy.ACCEPT_ONLY_FIRST_PARTY ) - assertEquals( - TrackingProtectionPolicy.CookiePolicy.ACCEPT_ONLY_FIRST_PARTY.id, - contentBlockingSettings.cookieBehavior - ) + assertEquals(CookiePolicy.ACCEPT_ONLY_FIRST_PARTY.id, contentBlockingSettings.cookieBehavior) engine.settings.trackingProtectionPolicy = TrackingProtectionPolicy.none() - assertEquals(TrackingProtectionPolicy.CookiePolicy.ACCEPT_ALL.id, contentBlockingSettings.cookieBehavior) + assertEquals(CookiePolicy.ACCEPT_ALL.id, contentBlockingSettings.cookieBehavior) } @Test @@ -416,8 +413,8 @@ class GeckoEngineTest { println(version) - assertTrue(version.major >= 68) - assertTrue(version.isAtLeast(68, 0, 0)) + assertTrue(version.major >= 69) + assertTrue(version.isAtLeast(69, 0, 0)) } private fun ContentBlocking.Settings.contains(vararg safeBrowsingPolicies: SafeBrowsingPolicy) = diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineViewTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineViewTest.kt index 2088d5b7735..d1391cbbab6 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineViewTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineViewTest.kt @@ -15,7 +15,8 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito +import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mozilla.geckoview.GeckoResult @@ -96,8 +97,8 @@ class GeckoEngineViewTest { engineView.render(engineSession) - verify(geckoView, Mockito.never()).releaseSession() - verify(engineSession, Mockito.never()).unregister(any()) + verify(geckoView, never()).releaseSession() + verify(engineSession, never()).unregister(any()) engineView.release() @@ -105,6 +106,26 @@ class GeckoEngineViewTest { verify(engineSession).unregister(any()) } + @Test + fun `View will rebind session if process gets killed`() { + val engineView = GeckoEngineView(context) + val engineSession = mock() + val geckoSession = mock() + val geckoView = mock() + + whenever(engineSession.geckoSession).thenReturn(geckoSession) + engineView.currentGeckoView = geckoView + + engineView.render(engineSession) + + reset(geckoView) + verify(geckoView, never()).setSession(geckoSession) + + engineView.observer.onProcessKilled() + + verify(geckoView).setSession(geckoSession) + } + @Test fun `View will rebind session if session crashed`() { val engineView = GeckoEngineView(context) @@ -117,8 +138,8 @@ class GeckoEngineViewTest { engineView.render(engineSession) - Mockito.reset(geckoView) - verify(geckoView, Mockito.never()).setSession(geckoSession) + reset(geckoView) + verify(geckoView, never()).setSession(geckoSession) engineView.observer.onCrash() diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoResultTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoResultTest.kt new file mode 100644 index 00000000000..df89bd3bca7 --- /dev/null +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoResultTest.kt @@ -0,0 +1,53 @@ +/* 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 mozilla.components.browser.engine.gecko + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.geckoview.GeckoResult + +@ExperimentalCoroutinesApi +@RunWith(AndroidJUnit4::class) +class GeckoResultTest { + + @Test + fun awaitWithResult() = runBlockingTest { + val result = GeckoResult.fromValue(42).await() + assertEquals(42, result) + } + + @Test(expected = IllegalStateException::class) + fun awaitWithException() = runBlockingTest { + GeckoResult.fromException(IllegalStateException()).await() + } + + @Test + fun fromResult() = runBlockingTest { + val result = launchGeckoResult { 42 } + + result.then { + assertEquals(42, it) + GeckoResult.fromValue(null) + }.await() + } + + @Test + fun fromException() = runBlockingTest { + val result = launchGeckoResult { throw IllegalStateException() } + + result.then({ + assertTrue("Invalid branch", false) + GeckoResult.fromValue(null) + }, { + assertTrue(it is IllegalStateException) + GeckoResult.fromValue(null) + }).await() + } +} diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/media/GeckoMediaTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/media/GeckoMediaTest.kt index 60d9a22fcd8..44e5c0b971e 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/media/GeckoMediaTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/media/GeckoMediaTest.kt @@ -80,6 +80,23 @@ class GeckoMediaTest { assertTrue(media.controller is GeckoMediaController) } + @Test + fun `GeckoMedia observer is notified when its playback state changes`() { + val mediaElement: MediaElement = mock() + + val media = GeckoMedia(mediaElement) + + val captor = argumentCaptor() + verify(mediaElement).delegate = captor.capture() + val delegate = captor.value + + val observer: Media.Observer = mock() + media.register(observer) + + delegate.onPlaybackStateChange(mediaElement, MediaElement.MEDIA_STATE_PLAYING) + verify(observer).onPlaybackStateChanged(media, Media.PlaybackState.PLAYING) + } + @Test fun `GeckoMedia exposes Metadata`() { val mediaElement: MediaElement = mock() diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/permission/GeckoPermissionRequestTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/permission/GeckoPermissionRequestTest.kt index 82c5e12d6ae..0b6c82070dd 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/permission/GeckoPermissionRequestTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/permission/GeckoPermissionRequestTest.kt @@ -114,26 +114,17 @@ class GeckoPermissionRequestTest { val videoCamera = MockMediaSource("videoCamera", "videoCamera", MediaSource.SOURCE_CAMERA, MediaSource.TYPE_VIDEO) - val videoBrowser = MockMediaSource("videoBrowser", "videoBrowser", - MediaSource.SOURCE_BROWSER, MediaSource.TYPE_VIDEO) - val videoApplication = MockMediaSource("videoApplication", "videoApplication", - MediaSource.SOURCE_APPLICATION, MediaSource.TYPE_VIDEO) val videoScreen = MockMediaSource("videoScreen", "videoScreen", MediaSource.SOURCE_SCREEN, MediaSource.TYPE_VIDEO) - val videoWindow = MockMediaSource("videoWindow", "videoWindow", - MediaSource.SOURCE_WINDOW, MediaSource.TYPE_VIDEO) val videoOther = MockMediaSource("videoOther", "videoOther", MediaSource.SOURCE_OTHER, MediaSource.TYPE_VIDEO) val audioSources = listOf(audioCapture, audioMicrophone, audioOther) - val videoSources = listOf(videoApplication, videoBrowser, videoCamera, videoOther, videoScreen, videoWindow) + val videoSources = listOf(videoCamera, videoOther, videoScreen) val mappedPermissions = listOf( Permission.ContentVideoCamera("videoCamera", "videoCamera"), - Permission.ContentVideoBrowser("videoBrowser", "videoBrowser"), - Permission.ContentVideoApplication("videoApplication", "videoApplication"), Permission.ContentVideoScreen("videoScreen", "videoScreen"), - Permission.ContentVideoWindow("videoWindow", "videoWindow"), Permission.ContentVideoOther("videoOther", "videoOther"), Permission.ContentAudioMicrophone("audioMicrophone", "audioMicrophone"), Permission.ContentAudioCapture("audioCapture", "audioCapture"), diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt index 286a361221d..8015574faeb 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/prompt/GeckoPromptDelegateTest.kt @@ -774,7 +774,6 @@ class GeckoPromptDelegateTest { @Test fun `Calling onColorPrompt must provide a Color PromptRequest`() { - val mockSession = GeckoEngineSession(mock()) var request: PromptRequest? = null var onConfirmWasCalled = false diff --git a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/webextension/GeckoWebExtensionTest.kt b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/webextension/GeckoWebExtensionTest.kt index 0fa72ec7b1f..467207ac086 100644 --- a/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/webextension/GeckoWebExtensionTest.kt +++ b/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/webextension/GeckoWebExtensionTest.kt @@ -2,12 +2,10 @@ * 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 mozilla.components.browser.engine.gecko.prompt +package mozilla.components.browser.engine.gecko.webextension import androidx.test.ext.junit.runners.AndroidJUnit4 import mozilla.components.browser.engine.gecko.GeckoEngineSession -import mozilla.components.browser.engine.gecko.webextension.GeckoPort -import mozilla.components.browser.engine.gecko.webextension.GeckoWebExtension import mozilla.components.concept.engine.webextension.MessageHandler import mozilla.components.concept.engine.webextension.Port import mozilla.components.support.test.argumentCaptor @@ -88,7 +86,7 @@ class GeckoWebExtensionTest { val extension = GeckoWebExtension("mozacTest", "url", true, nativeGeckoWebExt) assertFalse(extension.hasContentMessageHandler(session, "mozacTest")) extension.registerContentMessageHandler(session, "mozacTest", messageHandler) - verify(geckoSession).setMessageDelegate(messageDelegateCaptor.capture(), eq("mozacTest")) + verify(geckoSession).setMessageDelegate(eq(nativeGeckoWebExt), messageDelegateCaptor.capture(), eq("mozacTest")) // Verify messages are forwarded to message handler and return value passed on val message: Any = mock()