Commit 1e9b0308 authored by Sebastian Kaspari's avatar Sebastian Kaspari
Browse files

Issue #3616: (Merge day) browser-engine-gecko-beta (68) -> browser-engine-gecko-release (68)

parent 788c945b
......@@ -16,7 +16,7 @@ internal object GeckoVersions {
/**
* GeckoView Release Version.
*/
const val release_version = "67.0.20190613202504"
const val release_version = "68.0.20190711090008"
}
@Suppress("MaxLineLength")
......
......@@ -11,6 +11,7 @@ android {
defaultConfig {
minSdkVersion config.minSdkVersion
targetSdkVersion config.targetSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
......@@ -19,6 +20,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
exclude 'META-INF/proguard/androidx-annotations.pro'
}
}
dependencies {
......@@ -39,6 +44,7 @@ dependencies {
testImplementation Dependencies.testing_robolectric
testImplementation Dependencies.testing_mockito
testImplementation Dependencies.testing_mockwebserver
testImplementation Dependencies.testing_coroutines
testImplementation project(':support-test')
testImplementation project(':tooling-fetch-tests')
......
......@@ -6,6 +6,9 @@ package mozilla.components.browser.engine.gecko
import android.content.Context
import android.util.AttributeSet
import mozilla.components.browser.engine.gecko.integration.LocaleSettingUpdater
import mozilla.components.browser.engine.gecko.mediaquery.from
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
......@@ -14,11 +17,13 @@ import mozilla.components.concept.engine.EngineSessionState
import mozilla.components.concept.engine.EngineView
import mozilla.components.concept.engine.Settings
import mozilla.components.concept.engine.history.HistoryTrackingDelegate
import mozilla.components.concept.engine.mediaquery.PreferredColorScheme
import mozilla.components.concept.engine.utils.EngineVersion
import mozilla.components.concept.engine.webextension.WebExtension
import org.json.JSONObject
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoRuntimeSettings
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoWebExecutor
import java.lang.IllegalStateException
......@@ -34,6 +39,8 @@ class GeckoEngine(
) : Engine {
private val executor by lazy { executorProvider.invoke() }
private val localeUpdater = LocaleSettingUpdater(context, runtime)
init {
runtime.delegate = GeckoRuntime.Delegate {
// On shutdown: The runtime is shutting down (possibly because of an unrecoverable error state). We crash
......@@ -87,7 +94,7 @@ class GeckoEngine(
onSuccess: ((WebExtension) -> Unit),
onError: ((String, Throwable) -> Unit)
) {
GeckoWebExtension(id, url).also { ext ->
GeckoWebExtension(id, url, allowContentMessaging).also { ext ->
runtime.registerWebExtension(ext.nativeExtension).then({
onSuccess(ext)
GeckoResult<Void>()
......@@ -98,6 +105,29 @@ class GeckoEngine(
}
}
/**
* See [Engine.clearData].
*/
override fun clearData(
data: Engine.BrowsingData,
host: String?,
onSuccess: () -> Unit,
onError: (Throwable) -> Unit
) {
val flags = data.types.toLong()
if (host != null) {
runtime.storageController.clearDataFromHost(host, flags)
} else {
runtime.storageController.clearData(flags)
}.then({
onSuccess()
GeckoResult<Void>()
}, {
throwable -> onError(throwable)
GeckoResult<Void>()
})
}
override fun name(): String = "Gecko"
override val version: EngineVersion = EngineVersion.parse(org.mozilla.geckoview.BuildConfig.MOZILLA_VERSION)
......@@ -119,6 +149,13 @@ class GeckoEngine(
get() = runtime.settings.automaticFontSizeAdjustment
set(value) { runtime.settings.automaticFontSizeAdjustment = value }
override var automaticLanguageAdjustment: Boolean
get() = localeUpdater.enabled
set(value) {
localeUpdater.enabled = value
defaultSettings?.automaticLanguageAdjustment = value
}
override var trackingProtectionPolicy: TrackingProtectionPolicy?
get() = TrackingProtectionPolicy.select(runtime.settings.contentBlocking.categories)
set(value) {
......@@ -143,15 +180,66 @@ class GeckoEngine(
override var userAgentString: String?
get() = defaultSettings?.userAgentString ?: GeckoSession.getDefaultUserAgent()
set(value) { defaultSettings?.userAgentString = value }
override var preferredColorScheme: PreferredColorScheme
get() = PreferredColorScheme.from(runtime.settings.preferredColorScheme)
set(value) { runtime.settings.preferredColorScheme = value.toGeckoValue() }
override var allowAutoplayMedia: Boolean
get() = runtime.settings.autoplayDefault == GeckoRuntimeSettings.AUTOPLAY_DEFAULT_ALLOWED
set(value) {
runtime.settings.autoplayDefault = if (value) {
GeckoRuntimeSettings.AUTOPLAY_DEFAULT_ALLOWED
} else {
GeckoRuntimeSettings.AUTOPLAY_DEFAULT_BLOCKED
}
}
override var suspendMediaWhenInactive: Boolean
get() = defaultSettings?.suspendMediaWhenInactive ?: false
set(value) { defaultSettings?.suspendMediaWhenInactive = value }
override var fontInflationEnabled: Boolean?
get() = runtime.settings.fontInflationEnabled
set(value) {
// automaticFontSizeAdjustment is set to true by default, which
// will cause an exception if fontInflationEnabled is set
// (to either true or false). We therefore need to be able to
// set our built-in default value to null so that the exception
// is only thrown if an app is configured incorrectly but not
// if it uses default values.
value?.let {
runtime.settings.fontInflationEnabled = it
}
}
override var fontSizeFactor: Float?
get() = runtime.settings.fontSizeFactor
set(value) {
// automaticFontSizeAdjustment is set to true by default, which
// will cause an exception if fontSizeFactor is set as well.
// We therefore need to be able to set our built-in default value
// to null so that the exception is only thrown if an app is
// configured incorrectly but not if it uses default values.
value?.let {
runtime.settings.fontSizeFactor = it
}
}
}.apply {
defaultSettings?.let {
this.javascriptEnabled = it.javascriptEnabled
this.webFontsEnabled = it.webFontsEnabled
this.automaticFontSizeAdjustment = it.automaticFontSizeAdjustment
this.automaticLanguageAdjustment = it.automaticLanguageAdjustment
this.trackingProtectionPolicy = it.trackingProtectionPolicy
this.remoteDebuggingEnabled = it.remoteDebuggingEnabled
this.testingModeEnabled = it.testingModeEnabled
this.userAgentString = it.userAgentString
this.preferredColorScheme = it.preferredColorScheme
this.allowAutoplayMedia = it.allowAutoplayMedia
this.suspendMediaWhenInactive = it.suspendMediaWhenInactive
this.fontInflationEnabled = it.fontInflationEnabled
this.fontSizeFactor = it.fontSizeFactor
}
}
}
......@@ -5,13 +5,12 @@
package mozilla.components.browser.engine.gecko
import android.annotation.SuppressLint
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CompletableDeferred
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
import mozilla.components.browser.engine.gecko.prompt.GeckoPromptDelegate
import mozilla.components.browser.errorpages.ErrorType
......@@ -28,7 +27,6 @@ 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 mozilla.components.support.utils.ThreadUtils
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.ContentBlocking
import org.mozilla.geckoview.GeckoResult
......@@ -37,7 +35,7 @@ import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.NavigationDelegate
import org.mozilla.geckoview.GeckoSessionSettings
import org.mozilla.geckoview.WebRequestError
import java.lang.IllegalStateException
import kotlin.coroutines.CoroutineContext
/**
* Gecko-based EngineSession implementation.
......@@ -58,7 +56,10 @@ class GeckoEngineSession(
internal lateinit var geckoSession: GeckoSession
internal var currentUrl: String? = null
internal var scrollY: Int = 0
internal var job: Job = Job()
private var lastSessionState: GeckoSession.SessionState? = null
private var stateBeforeCrash: GeckoSession.SessionState? = null
/**
* See [EngineSession.settings]
......@@ -69,6 +70,9 @@ class GeckoEngineSession(
override var userAgentString: String?
get() = geckoSession.settings.userAgentOverride
set(value) { geckoSession.settings.userAgentOverride = value }
override var suspendMediaWhenInactive: Boolean
get() = geckoSession.settings.suspendMediaWhenInactive
set(value) { geckoSession.settings.suspendMediaWhenInactive = value }
}
private var initialLoad = true
......@@ -134,28 +138,9 @@ class GeckoEngineSession(
/**
* See [EngineSession.saveState]
*
* See https://bugzilla.mozilla.org/show_bug.cgi?id=1441810 for
* discussion on sync vs. async, where a decision was made that
* callers should provide synchronous wrappers, if needed. In case we're
* asking for the state when persisting, a separate (independent) thread
* is used so we're not blocking anything else. In case of calling this
* method from onPause or similar, we also want a synchronous response.
*/
override fun saveState(): EngineSessionState = runBlocking {
val state = CompletableDeferred<GeckoEngineSessionState>()
ThreadUtils.postToBackgroundThread {
geckoSession.saveState().then({ sessionState ->
state.complete(GeckoEngineSessionState(sessionState))
GeckoResult<Void>()
}, { throwable ->
state.completeExceptionally(throwable)
GeckoResult<Void>()
})
}
state.await()
override fun saveState(): EngineSessionState {
return GeckoEngineSessionState(lastSessionState)
}
/**
......@@ -267,6 +252,22 @@ class GeckoEngineSession(
geckoSession.exitFullScreen()
}
/**
* See [EngineSession.recoverFromCrash]
*/
@Synchronized
override fun recoverFromCrash(): Boolean {
val state = stateBeforeCrash
return if (state != null) {
geckoSession.restoreState(state)
stateBeforeCrash = null
true
} else {
false
}
}
/**
* See [EngineSession.close].
*/
......@@ -276,11 +277,6 @@ class GeckoEngineSession(
geckoSession.close()
}
override fun recoverFromCrash(): Boolean {
// No-op: Functionality requires GeckoView 68.0
return false
}
/**
* NavigationDelegate implementation for forwarding callbacks to observers of the session.
*/
......@@ -320,7 +316,22 @@ class GeckoEngineSession(
is InterceptionResponse.Url -> loadUrl(url)
}
}
return GeckoResult.fromValue(if (response != null) AllowOrDeny.DENY else AllowOrDeny.ALLOW)
return if (response != null) {
GeckoResult.fromValue(AllowOrDeny.DENY)
} else {
notifyObservers {
// As the name LoadRequest.isRedirect may imply this flag is about http redirects. The flag
// is "True if and only if the request was triggered by an HTTP redirect."
onLoadRequest(
url = request.uri,
triggeredByRedirect = request.isRedirect,
triggeredByWebContent = requestFromWebContent
)
}
GeckoResult.fromValue(AllowOrDeny.ALLOW)
}
}
override fun onCanGoForward(session: GeckoSession, canGoForward: Boolean) {
......@@ -393,6 +404,10 @@ class GeckoEngineSession(
onLoadingStateChange(false)
}
}
override fun onSessionStateChange(session: GeckoSession, sessionState: GeckoSession.SessionState) {
lastSessionState = sessionState
}
}
@Suppress("ComplexMethod")
......@@ -479,8 +494,12 @@ class GeckoEngineSession(
}
override fun onCrash(session: GeckoSession) {
stateBeforeCrash = lastSessionState
geckoSession.close()
createGeckoSession()
notifyObservers { onCrashStateChange(crashed = true) }
}
override fun onFullScreen(session: GeckoSession, fullScreen: Boolean) {
......@@ -561,6 +580,12 @@ class GeckoEngineSession(
}
}
private fun createScrollDelegate() = object : GeckoSession.ScrollDelegate {
override fun onScrollChanged(session: GeckoSession, scrollX: Int, scrollY: Int) {
this@GeckoEngineSession.scrollY = scrollY
}
}
@Suppress("ComplexMethod")
fun handleLongClick(elementSrc: String?, elementType: Int, uri: String? = null): HitResult? {
return when (elementType) {
......@@ -603,12 +628,9 @@ class GeckoEngineSession(
defaultSettings?.trackingProtectionPolicy?.let { enableTrackingProtection(it) }
defaultSettings?.requestInterceptor?.let { settings.requestInterceptor = it }
defaultSettings?.historyTrackingDelegate?.let { settings.historyTrackingDelegate = it }
defaultSettings?.testingModeEnabled?.let {
geckoSession.settings.fullAccessibilityTree = it
}
defaultSettings?.userAgentString?.let {
geckoSession.settings.userAgentOverride = it
}
defaultSettings?.testingModeEnabled?.let { geckoSession.settings.fullAccessibilityTree = it }
defaultSettings?.userAgentString?.let { geckoSession.settings.userAgentOverride = it }
defaultSettings?.suspendMediaWhenInactive?.let { geckoSession.settings.suspendMediaWhenInactive = it }
geckoSession.open(runtime)
......@@ -619,6 +641,8 @@ class GeckoEngineSession(
geckoSession.permissionDelegate = createPermissionDelegate()
geckoSession.promptDelegate = GeckoPromptDelegate(this)
geckoSession.historyDelegate = createHistoryDelegate()
geckoSession.mediaDelegate = GeckoMediaDelegate(this)
geckoSession.scrollDelegate = createScrollDelegate()
}
companion object {
......@@ -631,7 +655,7 @@ class GeckoEngineSession(
* Provides an ErrorType corresponding to the error code provided.
*/
@Suppress("ComplexMethod")
internal fun geckoErrorToErrorType(@WebRequestError.Error errorCode: Int) =
internal fun geckoErrorToErrorType(errorCode: Int) =
when (errorCode) {
WebRequestError.ERROR_UNKNOWN -> ErrorType.UNKNOWN
WebRequestError.ERROR_SECURITY_SSL -> ErrorType.ERROR_SECURITY_SSL
......
......@@ -26,7 +26,10 @@ class GeckoEngineSessionState internal constructor(
companion object {
fun fromJSON(json: JSONObject): GeckoEngineSessionState = try {
val state = json.getString(GECKO_STATE_KEY)
GeckoEngineSessionState(GeckoSession.SessionState(state))
GeckoEngineSessionState(
GeckoSession.SessionState.fromString(state)
)
} catch (e: JSONException) {
GeckoEngineSessionState(null)
}
......
......@@ -8,9 +8,11 @@ import android.content.Context
import android.graphics.Bitmap
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.core.view.ViewCompat
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineView
import mozilla.components.concept.engine.permission.PermissionRequest
import org.mozilla.geckoview.GeckoResult
/**
......@@ -21,6 +23,7 @@ class GeckoEngineView @JvmOverloads constructor(
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr), EngineView {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal var currentGeckoView = object : NestedGeckoView(context) {
override fun onDetachedFromWindow() {
// We are releasing the session before GeckoView gets detached from the window. Otherwise
......@@ -33,9 +36,25 @@ class GeckoEngineView @JvmOverloads constructor(
// Explicitly mark this view as important for autofill. The default "auto" doesn't seem to trigger any
// autofill behavior for us here.
@Suppress("WrongConstant")
ViewCompat.setImportantForAutofill(this, 0x1 /* View.IMPORTANT_FOR_AUTOFILL_YES */)
ViewCompat.setImportantForAutofill(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES)
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal val observer = object : EngineSession.Observer {
override fun onCrashStateChange(crashed: Boolean) {
if (crashed) {
// When crashing the previous GeckoSession is no longer usable. Internally GeckoEngineSession will
// create a new instance. This means we will need to tell GeckoView about this new GeckoSession:
currentSession?.let { currentGeckoView.setSession(it.geckoSession) }
}
}
override fun onAppPermissionRequest(permissionRequest: PermissionRequest) = Unit
override fun onContentPermissionRequest(permissionRequest: PermissionRequest) = Unit
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal var currentSession: GeckoEngineSession? = null
init {
// Currently this is just a FrameLayout with a single GeckoView instance. Eventually this
// implementation should handle at least two GeckoView so that we can switch between
......@@ -45,9 +64,16 @@ class GeckoEngineView @JvmOverloads constructor(
/**
* Render the content of the given session.
*/
@Synchronized
override fun render(session: EngineSession) {
val internalSession = session as GeckoEngineSession
currentSession?.apply { unregister(observer) }
currentSession = session.apply {
register(observer)
}
if (currentGeckoView.session != internalSession.geckoSession) {
currentGeckoView.session?.let {
// Release a previously assigned session. Otherwise GeckoView will close it
......@@ -61,11 +87,27 @@ class GeckoEngineView @JvmOverloads constructor(
@Synchronized
override fun release() {
currentSession?.apply { unregister(observer) }
currentSession = null
currentGeckoView.releaseSession()
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
release()
}
override fun canScrollVerticallyUp() = currentSession?.let { it.scrollY > 0 } != false
override fun canScrollVerticallyDown() = true // waiting for this issue https://bugzilla.mozilla.org/show_bug.cgi?id=1507569
override fun setVerticalClipping(clippingHeight: Int) {
currentGeckoView.setVerticalClipping(clippingHeight)
}
override fun captureThumbnail(onFinish: (Bitmap?) -> Unit) {
val geckoResult = currentGeckoView.capturePixels()
geckoResult.then({ bitmap ->
......@@ -76,8 +118,4 @@ class GeckoEngineView @JvmOverloads constructor(
GeckoResult<Void>()
})
}
override fun setVerticalClipping(clippingHeight: Int) {
// no-op: requires GeckoView 68.0
}
}
/* 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.integration
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.core.os.LocaleListCompat as LocaleList
import org.mozilla.geckoview.GeckoRuntime
/**
* Class to set the locales setting for geckoview, updating from the locale of the device.
*/
class LocaleSettingUpdater(
private val context: Context,
private val runtime: GeckoRuntime
) : SettingUpdater<Array<String>>() {
override var value: Array<String> = findValue()
set(value) {
runtime.settings.locales = value
field = value
}
private val localeChangedReceiver by lazy {
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
updateValue()
}
}
}
override fun registerForUpdates() {
context.registerReceiver(localeChangedReceiver, IntentFilter(Intent.ACTION_LOCALE_CHANGED))
}
override fun unregisterForUpdates() {
context.unregisterReceiver(localeChangedReceiver)
}
override fun findValue(): Array<String> {
val localeList = LocaleList.getAdjustedDefault()
return arrayOfNulls<Unit>(localeList.size())
.mapIndexedNotNull { i, _ -> localeList.get(i).toLanguageTag() }
.toTypedArray()
}
}
package mozilla.components.browser.engine.gecko.integration
abstract class SettingUpdater<T> {
/**
* Toggle the automatic tracking of a setting derived from the device state.