GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

Commit ad26d7dd authored by Alex Catarineu's avatar Alex Catarineu Committed by Matthew Finkel
Browse files

Modify Add-on support

Bug 40030: Install HTTPS Everywhere and NoScript addons on startup

HTTPS Everywhere is installed as a builtin extension and NoScript as
a regular AMO addon. To avoid unnecessary I/O we only install NoScript
the first time, and rely on the browser addon updating mechanism for
keeping up with new versions. This is the same behaviour that was
implemented in the Fennec-based Tor Browser, where it was installed
as a "distribution addon", which also only occurred once.

Bug 40062: HTTPS Everywhere is not shown as installed

Also 40070: Consider storing the list of recommended addons

This implements our own AddonsProvider, which loads the list of
available addons from assets instead of fetching it from an
endpoint. In this list, we replace https-everywhere by
our https-everywhere-eff, so that the EFF one is shown as installed
in the addons list and the AMO one is not displayed.

Also, we hide the uninstall button for builtin addons.

Bug 40058: Hide option for disallowing addon in private mode
parent c8bec06e
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -21,6 +21,7 @@ import kotlinx.coroutines.launch
import mozilla.components.feature.addons.Addon
import mozilla.components.feature.addons.AddonManagerException
import mozilla.components.feature.addons.ui.translateName
import mozilla.components.support.webextensions.WebExtensionSupport.installedExtensions
import org.mozilla.fenix.GleanMetrics.Addons
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
......@@ -114,7 +115,7 @@ class InstalledAddonDetailsFragment : Fragment() {
runIfFragmentIsAttached {
this.addon = it
switch.isClickable = true
privateBrowsingSwitch.isVisible = it.isEnabled()
privateBrowsingSwitch.isVisible = false
privateBrowsingSwitch.isChecked = it.isAllowedInPrivateBrowsing()
switch.setText(R.string.mozac_feature_addons_enabled)
view.settings.isVisible = shouldSettingsBeVisible()
......@@ -241,7 +242,7 @@ class InstalledAddonDetailsFragment : Fragment() {
private fun bindAllowInPrivateBrowsingSwitch(view: View) {
val switch = view.allow_in_private_browsing_switch
switch.isChecked = addon.isAllowedInPrivateBrowsing()
switch.isVisible = addon.isEnabled()
switch.isVisible = false
switch.setOnCheckedChangeListener { v, isChecked ->
val addonManager = v.context.components.addonManager
switch.isClickable = false
......@@ -267,6 +268,8 @@ class InstalledAddonDetailsFragment : Fragment() {
}
}
private fun bindRemoveButton(view: View) {
val isBuiltin = installedExtensions[addon.id]?.isBuiltIn() ?: false
view.remove_add_on.isVisible = !isBuiltin
view.remove_add_on.setOnClickListener {
setAllInteractiveViewsClickable(view, false)
requireContext().components.addonManager.uninstallAddon(
......
......@@ -10,7 +10,6 @@ import android.content.Intent
import androidx.core.net.toUri
import com.google.android.play.core.review.ReviewManagerFactory
import mozilla.components.feature.addons.AddonManager
import mozilla.components.feature.addons.amo.AddonCollectionProvider
import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker
import mozilla.components.feature.addons.migration.SupportedAddonsChecker
import mozilla.components.feature.addons.update.AddonUpdater
......@@ -89,32 +88,7 @@ class Components(private val context: Context) {
}
val addonCollectionProvider by lazyMonitored {
// Check if we have a customized (overridden) AMO collection (only supported in Nightly)
if (Config.channel.isNightlyOrDebug && context.settings().amoCollectionOverrideConfigured()) {
AddonCollectionProvider(
context,
core.client,
collectionUser = context.settings().overrideAmoUser,
collectionName = context.settings().overrideAmoCollection
)
}
// Use build config otherwise
else if (!BuildConfig.AMO_COLLECTION_USER.isNullOrEmpty() &&
!BuildConfig.AMO_COLLECTION_NAME.isNullOrEmpty()
) {
AddonCollectionProvider(
context,
core.client,
serverURL = BuildConfig.AMO_SERVER_URL,
collectionUser = BuildConfig.AMO_COLLECTION_USER,
collectionName = BuildConfig.AMO_COLLECTION_NAME,
maxCacheAgeInMinutes = AMO_COLLECTION_MAX_CACHE_AGE
)
}
// Fall back to defaults
else {
AddonCollectionProvider(context, core.client, maxCacheAgeInMinutes = AMO_COLLECTION_MAX_CACHE_AGE)
}
TorAddonCollectionProvider(context, core.client)
}
val appStartupTelemetry by lazyMonitored { AppStartupTelemetry(analytics.metrics) }
......
......@@ -134,6 +134,8 @@ class Core(
WebCompatFeature.install(it)
WebCompatReporterFeature.install(it, "fenix")
}
TorBrowserFeatures.install(context, it)
}
}
......
/* 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/. */
// Copyright (c) 2020, The Tor Project, Inc.
package org.mozilla.fenix.components
import android.content.Context
import android.graphics.Bitmap
import kotlinx.coroutines.withContext
import mozilla.components.concept.fetch.Client
import mozilla.components.feature.addons.Addon
import kotlinx.coroutines.Dispatchers
import mozilla.components.feature.addons.amo.AddonCollectionProvider
import java.io.IOException
internal const val COLLECTION_NAME = "tor_browser_collection"
internal const val ALLOWED_ADDONS_PATH = "allowed_addons.json"
internal const val MAX_CACHE_AGE = 1000L * 365L * 24L * 60L // 1000 years
class TorAddonCollectionProvider(
private val context: Context,
client: Client
) : AddonCollectionProvider(
context, client, serverURL = "",
collectionName = COLLECTION_NAME,
maxCacheAgeInMinutes = MAX_CACHE_AGE
) {
private var isCacheLoaded = false
@Throws(IOException::class)
override suspend fun getAvailableAddons(
allowCache: Boolean,
readTimeoutInSeconds: Long?,
language: String?
): List<Addon> {
ensureCache(language)
return super.getAvailableAddons(true, readTimeoutInSeconds, language)
}
@Throws(IOException::class)
override suspend fun getAddonIconBitmap(addon: Addon): Bitmap? {
// super.getAddonIconBitmap does not look at the cache, so calling
// ensureCache here is not helpful. In addition, now the cache depends
// on a language, and that isn't available right now.
// ensureCache(language)
return super.getAddonIconBitmap(addon)
}
@Throws(IOException::class)
private suspend fun ensureCache(language: String?) {
if (isCacheLoaded) {
return
}
return withContext(Dispatchers.IO) {
val data = context.assets.open(ALLOWED_ADDONS_PATH).bufferedReader().use {
it.readText()
}
writeToDiskCache(data, language)
isCacheLoaded = true
}
}
}
/* 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/. */
// Copyright (c) 2020, The Tor Project, Inc.
package org.mozilla.fenix.components
import android.content.Context
import mozilla.components.concept.engine.webextension.WebExtension
import mozilla.components.concept.engine.webextension.WebExtensionRuntime
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.ext.settings
import java.io.IOException
object TorBrowserFeatures {
private val logger = Logger("torbrowser-features")
private fun installNoScript(
context: Context,
runtime: WebExtensionRuntime,
onSuccess: ((WebExtension) -> Unit),
onError: ((Throwable) -> Unit)
) {
/**
* Copy the xpi from assets to cacheDir, we do not care if the file is later deleted.
*/
val addonPath =
context.cacheDir.resolve("{73a6fe31-595d-460b-a920-fcc0f8843232}.xpi")
try {
context.assets.open("extensions/{73a6fe31-595d-460b-a920-fcc0f8843232}.xpi")
.use { inStream ->
addonPath.outputStream().use { outStream ->
inStream.copyTo(outStream)
}
}
} catch (throwable: IOException) {
onError(throwable)
return
}
/**
* Install with a file:// URI pointing to the temp location where the addon was copied to.
*/
runtime.installWebExtension(
id = "{73a6fe31-595d-460b-a920-fcc0f8843232}",
url = addonPath.toURI().toString(),
onSuccess = { extension ->
runtime.setAllowedInPrivateBrowsing(
extension,
true,
onSuccess,
onError
)
},
onError = { _, throwable -> onError(throwable) })
}
private fun installHTTPSEverywhere(
runtime: WebExtensionRuntime,
onSuccess: ((WebExtension) -> Unit),
onError: ((Throwable) -> Unit)
) {
runtime.installWebExtension(
id = "https-everywhere-eff@eff.org",
url = "resource://android/assets/extensions/https-everywhere/",
onSuccess = onSuccess,
onError = { _, throwable -> onError(throwable) }
)
}
fun install(context: Context, runtime: WebExtensionRuntime) {
/**
* Install HTTPS Everywhere as a builtin addon, with a resource://android/ URI.
* No signatures will be checked/required and there will be no automatic
* extension updates. It's ok to always try to install, since for builtin extensions it will
* be checked internally whether it is necessary to install or not: it will only be done
* if this is the first time or if it's a newer version.
*/
installHTTPSEverywhere(
runtime,
onSuccess = {
logger.debug("HTTPS Everywhere extension was installed successfully")
},
onError = { throwable ->
logger.error("Could not install HTTPS Everywhere extension", throwable)
}
)
/**
* Install NoScript as a user WebExtension if we have not already done so.
* AMO signature is checked, and AMO automatic updates will work. The extension should
* behave as if the user had installed it manually.
*/
if (!context.settings().noscriptInstalled) {
installNoScript(
context,
runtime,
onSuccess = {
context.settings().noscriptInstalled = true
logger.debug("NoScript extension was installed successfully")
},
onError = { throwable ->
logger.error("Could not install NoScript extension", throwable)
}
)
}
}
}
......@@ -1031,4 +1031,9 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = false,
featureFlag = FeatureFlags.addressesFeature
)
var noscriptInstalled by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_noscript_installed),
default = false
)
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment