Verified Commit ba982207 authored by ma1's avatar ma1 Committed by Pier Angelo Vendrame
Browse files

[android] Modify add-on support

Bug 41160: One-time ultimate switch Tor Browser Android to HTTPS-Only.
Bug 41159: Remove HTTPS-Everywhere extension from Tor Browser Android.

Bug 41094: Enable HTTPS-Only Mode by default in Tor Browser Android.

Turn shouldUseHttpsOnly's default to true.

Bug 40225: Bundled extensions don't get updated with Android Tor
           Browser updates.

Bug 40030: Install NoScript addon on startup.

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.

Also, we hide the uninstall button for builtin addons.

Bug 40058: Hide option for disallowing addon in private mode
parent 380c3c46
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -128,7 +128,7 @@ class BrowserRobot {
            verifyUrl("/releaseNotes")
        } catch (e: AssertionError) {
            Log.i(TAG, "verifyWhatsNewURL: AssertionError caught, checking redirect URL")
            verifyUrl(SupportUtils.WHATS_NEW_URL)
            verifyUrl(SupportUtils.getTorWhatsNewUrl())
        }
    }

+5 −2
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import mozilla.components.feature.addons.AddonManager
import mozilla.components.feature.addons.AddonManagerException
import mozilla.components.feature.addons.ui.translateName
import org.mozilla.fenix.BuildConfig
import mozilla.components.support.webextensions.WebExtensionSupport.installedExtensions
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.FragmentInstalledAddOnDetailsBinding
@@ -165,7 +166,7 @@ class InstalledAddonDetailsFragment : Fragment() {
                        runIfFragmentIsAttached {
                            this.addon = it
                            switch.isClickable = true
                            privateBrowsingSwitch.isVisible = it.isEnabled()
                            privateBrowsingSwitch.isVisible = false
                            privateBrowsingSwitch.isChecked =
                                it.incognito != Addon.Incognito.NOT_ALLOWED && it.isAllowedInPrivateBrowsing()
                            binding.settings.isVisible = shouldSettingsBeVisible()
@@ -265,7 +266,7 @@ class InstalledAddonDetailsFragment : Fragment() {
    @VisibleForTesting
    internal fun bindAllowInPrivateBrowsingSwitch() {
        val switch = providePrivateBrowsingSwitch()
        switch.isVisible = addon.isEnabled()
        switch.isVisible = false

        if (addon.incognito == Addon.Incognito.NOT_ALLOWED) {
            switch.isChecked = false
@@ -366,6 +367,8 @@ class InstalledAddonDetailsFragment : Fragment() {
    }

    private fun bindRemoveButton() {
        val isBuiltin = installedExtensions[addon.id]?.isBuiltIn() ?: false
        binding.removeAddOn.isVisible = !isBuiltin
        binding.removeAddOn.setOnClickListener {
            setAllInteractiveViewsClickable(binding, false)
            requireContext().components.addonManager.uninstallAddon(
+2 −0
Original line number Diff line number Diff line
@@ -176,6 +176,8 @@ class Core(
//          if (Config.channel.isNightlyOrDebug || Config.channel.isBeta) {
//              WebCompatReporterFeature.install(it, "fenix")
//          }

            TorBrowserFeatures.install(context, it)
        }
    }

+145 −0
Original line number Diff line number Diff line
/* 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 kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.components.concept.engine.webextension.WebExtension
import mozilla.components.concept.engine.webextension.WebExtensionRuntime
import mozilla.components.support.webextensions.WebExtensionSupport
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.tor.TorEvents

object TorBrowserFeatures {
    private val logger = Logger("torbrowser-features")
    private const val NOSCRIPT_ID = "{73a6fe31-595d-460b-a920-fcc0f8843232}"

    private fun installNoScript(
        runtime: WebExtensionRuntime,
        onSuccess: ((WebExtension) -> Unit),
        onError: ((Throwable) -> Unit)
    ) {

        runtime.installBuiltInWebExtension(
            id = NOSCRIPT_ID,
            url = "resource://android/assets/extensions/" + NOSCRIPT_ID + ".xpi",
            onSuccess = { extension ->
                runtime.setAllowedInPrivateBrowsing(
                    extension,
                    true,
                    onSuccess,
                    onError
                )
            },
            onError = { throwable -> onError(throwable) })
    }

    @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
    private fun uninstallHTTPSEverywhere(
        runtime: WebExtensionRuntime,
        onSuccess: (() -> Unit),
        onError: ((Throwable) -> Unit)
    ) {
        // Wait for WebExtensionSupport on the I/O thread to avoid deadlocks.
        GlobalScope.launch(Dispatchers.IO) {
            WebExtensionSupport.awaitInitialization()
            // Back to the main thread.
            withContext(Dispatchers.Main) {
                val extension =
                    WebExtensionSupport.installedExtensions["https-everywhere-eff@eff.org"]
                        ?: return@withContext onSuccess() // Fine, nothing to uninstall.
                runtime.uninstallWebExtension(
                    extension,
                    onSuccess = onSuccess,
                    onError = { _, throwable -> onError(throwable) }
                )
            }
        }
    }

    fun install(context: Context, runtime: WebExtensionRuntime) {
        val settings = context.settings()
        /**
         * Remove HTTPS Everywhere if we didn't yet.
         */
        if (!settings.httpsEverywhereRemoved) {
            /**
             * Ensure HTTPS-Only is enabled.
             */
            settings.shouldUseHttpsOnly = true
            settings.shouldUseHttpsOnlyInAllTabs = true
            uninstallHTTPSEverywhere(
                runtime,
                onSuccess = {
                    settings.httpsEverywhereRemoved = true
                    logger.debug("HTTPS Everywhere extension was uninstalled successfully")
                },
                onError = { throwable ->
                    logger.error("Could not uninstall HTTPS Everywhere extension", throwable)
                }
            )
        }
        /**
         *  Install NoScript as a user WebExtension if we have not already done so.
         *  AMO signature is checked, but automatic updates still need to be enabled.
         */
        if (!settings.noscriptInstalled) {
            installNoScript(
                runtime,
                onSuccess = {
                    settings.noscriptInstalled = true
                    logger.debug("NoScript extension was installed successfully")
                },
                onError = { throwable ->
                    logger.error("Could not install NoScript extension", throwable)
                }
            )
        }

        /**
         *  Enable automatic updates for NoScript and, if we've not done it yet, force a
         *  one-time immediate update check, in order to upgrade old profiles and ensure we've got
         *  the latest stable AMO version available on first startup.
         *  We will do it as soon as the Tor is connected, to prevent early addonUpdater activation
         *  causing automatic update checks failures (components.addonUpdater being a lazy prop).
         *  The extension, from then on, should behave as if the user had installed it manually.
         */
        context.components.torController.registerTorListener(object : TorEvents {
            override fun onTorConnected() {
                context.components.torController.unregisterTorListener(this)
                // Enable automatic updates. This must be done on every startup (tor-browser#42353)
                context.components.addonUpdater.registerForFutureUpdates(NOSCRIPT_ID)
                // Force a one-time immediate update check for older installations
                if (settings.noscriptUpdated < 2) {
                    context.components.addonUpdater.update(NOSCRIPT_ID)
                    settings.noscriptUpdated = 2
                }
            }

            @SuppressWarnings("EmptyFunctionBlock")
            override fun onTorConnecting() {
            }

            @SuppressWarnings("EmptyFunctionBlock")
            override fun onTorStopped() {
            }

                @SuppressWarnings("EmptyFunctionBlock")
                override fun onTorStatusUpdate(entry: String?, status: String?) {
                }
            })
        }
    }


}
+15 −1
Original line number Diff line number Diff line
@@ -620,7 +620,7 @@ class Settings(private val appContext: Context) : PreferencesHolder {

    var shouldUseHttpsOnly by booleanPreference(
        appContext.getPreferenceKey(R.string.pref_key_https_only),
        default = false,
        default = true
    )

    var shouldUseHttpsOnlyInAllTabs by booleanPreference(
@@ -1930,6 +1930,20 @@ class Settings(private val appContext: Context) : PreferencesHolder {
    var growthEarlySearchUsed by booleanPreference(
        key = appContext.getPreferenceKey(R.string.pref_key_growth_early_search),
        default = false,

    var noscriptInstalled by booleanPreference(
        appContext.getPreferenceKey(R.string.pref_key_noscript_installed),
        default = false
    )

    var noscriptUpdated by intPreference(
        appContext.getPreferenceKey(R.string.pref_key_noscript_updated),
        default = 0
    )

    var httpsEverywhereRemoved by booleanPreference(
        appContext.getPreferenceKey(R.string.pref_key_https_everywhere_removed),
        default = false
    )

    /**
Loading