ma1 pushed to branch tor-browser-128.2.0esr-14.0-1 at The Tor Project / Applications / Tor Browser
Commits:
-
4eb9b64f
by hackademix at 2024-09-11T21:59:25+02:00
6 changed files:
- mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/webextension/GeckoWebExtension.kt
- mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/webextension/WebExtension.kt
- mobile/android/android-components/components/support/webextensions/src/main/java/mozilla/components/support/webextensions/WebExtensionSupport.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/TorBrowserFeatures.kt
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebExtensionController.java
Changes:
... | ... | @@ -393,6 +393,7 @@ class GeckoWebExtension( |
393 | 393 | |
394 | 394 | override fun isAllowedInPrivateBrowsing(): Boolean {
|
395 | 395 | return isBuiltIn() || nativeExtension.metaData.allowedInPrivateBrowsing
|
396 | + || isBundled()
|
|
396 | 397 | }
|
397 | 398 | |
398 | 399 | override suspend fun loadIcon(size: Int): Bitmap? {
|
... | ... | @@ -164,6 +164,14 @@ abstract class WebExtension( |
164 | 164 | */
|
165 | 165 | open fun isBuiltIn(): Boolean = Uri.parse(url).scheme == "resource"
|
166 | 166 | |
167 | + /**
|
|
168 | + * Checks whether or not this extension is bundled with this browser,
|
|
169 | + * but otherwise behaves as an unprivileged (non built-in) extension,
|
|
170 | + * except it cannot be disabled or uninstalled from the UI (e.g.
|
|
171 | + * NoScript in the Tor Browser).
|
|
172 | + */
|
|
173 | + open fun isBundled(): Boolean = id == "{73a6fe31-595d-460b-a920-fcc0f8843232}"
|
|
174 | + |
|
167 | 175 | /**
|
168 | 176 | * Checks whether or not this extension is enabled.
|
169 | 177 | */
|
... | ... | @@ -234,6 +234,7 @@ object WebExtensionSupport { |
234 | 234 | // when the add-on has already been installed, we don't need to show anything
|
235 | 235 | // either.
|
236 | 236 | val shouldDispatchAction = !installedExtensions.containsKey(extension.id) && !extension.isBuiltIn()
|
237 | + && !extension.isBundled()
|
|
237 | 238 | registerInstalledExtension(store, extension)
|
238 | 239 | if (shouldDispatchAction) {
|
239 | 240 | store.dispatch(
|
... | ... | @@ -44,6 +44,8 @@ class InstalledAddonDetailsFragment : Fragment() { |
44 | 44 | |
45 | 45 | private var _binding: FragmentInstalledAddOnDetailsBinding? = null
|
46 | 46 | |
47 | + private var isBundledAddon = false;
|
|
48 | + |
|
47 | 49 | override fun onCreateView(
|
48 | 50 | inflater: LayoutInflater,
|
49 | 51 | container: ViewGroup?,
|
... | ... | @@ -51,6 +53,7 @@ class InstalledAddonDetailsFragment : Fragment() { |
51 | 53 | ): View {
|
52 | 54 | if (!::addon.isInitialized) {
|
53 | 55 | addon = AddonDetailsFragmentArgs.fromBundle(requireNotNull(arguments)).addon
|
56 | + isBundledAddon = installedExtensions[addon.id]?.isBundled() ?: false
|
|
54 | 57 | }
|
55 | 58 | |
56 | 59 | setBindingAndBindUI(
|
... | ... | @@ -148,6 +151,7 @@ class InstalledAddonDetailsFragment : Fragment() { |
148 | 151 | // When the ad-on is blocklisted or not correctly signed, we do not want to enable the toggle switch
|
149 | 152 | // because users shouldn't be able to re-enable an add-on in this state.
|
150 | 153 | if (
|
154 | + isBundledAddon ||
|
|
151 | 155 | addon.isDisabledAsBlocklisted() ||
|
152 | 156 | addon.isDisabledAsNotCorrectlySigned() ||
|
153 | 157 | addon.isDisabledAsIncompatible()
|
... | ... | @@ -303,6 +307,7 @@ class InstalledAddonDetailsFragment : Fragment() { |
303 | 307 | }
|
304 | 308 | |
305 | 309 | private fun bindReportButton() {
|
310 | + binding.reportAddOn.isVisible = !isBundledAddon
|
|
306 | 311 | binding.reportAddOn.setOnClickListener {
|
307 | 312 | val shouldCreatePrivateSession = (activity as HomeActivity).browsingModeManager.mode.isPrivate
|
308 | 313 | |
... | ... | @@ -367,8 +372,7 @@ class InstalledAddonDetailsFragment : Fragment() { |
367 | 372 | }
|
368 | 373 | |
369 | 374 | private fun bindRemoveButton() {
|
370 | - val isBuiltin = installedExtensions[addon.id]?.isBuiltIn() ?: false
|
|
371 | - binding.removeAddOn.isVisible = !isBuiltin
|
|
375 | + binding.removeAddOn.isVisible = !isBundledAddon
|
|
372 | 376 | binding.removeAddOn.setOnClickListener {
|
373 | 377 | setAllInteractiveViewsClickable(binding, false)
|
374 | 378 | requireContext().components.addonManager.uninstallAddon(
|
... | ... | @@ -6,12 +6,14 @@ |
6 | 6 | |
7 | 7 | package org.mozilla.fenix.components
|
8 | 8 | |
9 | +import android.os.StrictMode
|
|
9 | 10 | import android.content.Context
|
10 | 11 | import kotlinx.coroutines.DelicateCoroutinesApi
|
11 | 12 | import kotlinx.coroutines.Dispatchers
|
12 | 13 | import kotlinx.coroutines.GlobalScope
|
13 | 14 | import kotlinx.coroutines.launch
|
14 | 15 | import kotlinx.coroutines.withContext
|
16 | +import java.io.IOException
|
|
15 | 17 | import mozilla.components.concept.engine.webextension.WebExtension
|
16 | 18 | import mozilla.components.concept.engine.webextension.WebExtensionRuntime
|
17 | 19 | import mozilla.components.support.webextensions.WebExtensionSupport
|
... | ... | @@ -25,14 +27,39 @@ object TorBrowserFeatures { |
25 | 27 | private const val NOSCRIPT_ID = "{73a6fe31-595d-460b-a920-fcc0f8843232}"
|
26 | 28 | |
27 | 29 | private fun installNoScript(
|
30 | + context: Context,
|
|
28 | 31 | runtime: WebExtensionRuntime,
|
29 | 32 | onSuccess: ((WebExtension) -> Unit),
|
30 | 33 | onError: ((Throwable) -> Unit)
|
31 | 34 | ) {
|
35 | + /**
|
|
36 | + * Copy the xpi from assets to cacheDir, we do not care if the file is later deleted.
|
|
37 | + */
|
|
38 | + val xpiName = "$NOSCRIPT_ID.xpi"
|
|
39 | + val addonPath = context.cacheDir.resolve(xpiName)
|
|
40 | + val policy = StrictMode.getThreadPolicy()
|
|
41 | + try {
|
|
42 | + context.assets.open("extensions/$xpiName")
|
|
43 | + .use { inStream ->
|
|
44 | + // we don't want penaltyDeath() on disk write
|
|
45 | + StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX)
|
|
32 | 46 | |
33 | - runtime.installBuiltInWebExtension(
|
|
34 | - id = NOSCRIPT_ID,
|
|
35 | - url = "resource://android/assets/extensions/" + NOSCRIPT_ID + ".xpi",
|
|
47 | + addonPath.outputStream().use { outStream ->
|
|
48 | + inStream.copyTo(outStream)
|
|
49 | + }
|
|
50 | + }
|
|
51 | + } catch (throwable: IOException) {
|
|
52 | + onError(throwable)
|
|
53 | + return
|
|
54 | + } finally {
|
|
55 | + StrictMode.setThreadPolicy(policy)
|
|
56 | + }
|
|
57 | + |
|
58 | + /**
|
|
59 | + * Install with a file:// URI pointing to the temp location where the addon was copied to.
|
|
60 | + */
|
|
61 | + runtime.installWebExtension(
|
|
62 | + url = addonPath.toURI().toString(),
|
|
36 | 63 | onSuccess = { extension ->
|
37 | 64 | runtime.setAllowedInPrivateBrowsing(
|
38 | 65 | extension,
|
... | ... | @@ -95,6 +122,7 @@ object TorBrowserFeatures { |
95 | 122 | */
|
96 | 123 | if (!settings.noscriptInstalled) {
|
97 | 124 | installNoScript(
|
125 | + context,
|
|
98 | 126 | runtime,
|
99 | 127 | onSuccess = {
|
100 | 128 | settings.noscriptInstalled = true
|
... | ... | @@ -1166,6 +1166,27 @@ public class WebExtensionController { |
1166 | 1166 | });
|
1167 | 1167 | }
|
1168 | 1168 | |
1169 | + private boolean isBundledExtension(final String extensionId) {
|
|
1170 | + return "{73a6fe31-595d-460b-a920-fcc0f8843232}".equals(extensionId);
|
|
1171 | + }
|
|
1172 | + |
|
1173 | + private boolean promptBypass(final WebExtension extension, final EventCallback callback) {
|
|
1174 | + // allow bundled extensions, e.g. NoScript, to be installed with no prompt
|
|
1175 | + if (isBundledExtension(extension.id)) {
|
|
1176 | + callback.resolveTo(
|
|
1177 | + GeckoResult.allow().map(
|
|
1178 | + allowOrDeny -> {
|
|
1179 | + final GeckoBundle response = new GeckoBundle(1);
|
|
1180 | + response.putBoolean("allow", true);
|
|
1181 | + return response;
|
|
1182 | + }
|
|
1183 | + )
|
|
1184 | + );
|
|
1185 | + return true;
|
|
1186 | + }
|
|
1187 | + return false;
|
|
1188 | + }
|
|
1189 | + |
|
1169 | 1190 | private void installPrompt(final GeckoBundle message, final EventCallback callback) {
|
1170 | 1191 | final GeckoBundle extensionBundle = message.getBundle("extension");
|
1171 | 1192 | if (extensionBundle == null
|
... | ... | @@ -1181,6 +1202,10 @@ public class WebExtensionController { |
1181 | 1202 | |
1182 | 1203 | final WebExtension extension = new WebExtension(mDelegateControllerProvider, extensionBundle);
|
1183 | 1204 | |
1205 | + if (promptBypass(extension, callback)) {
|
|
1206 | + return;
|
|
1207 | + }
|
|
1208 | + |
|
1184 | 1209 | if (mPromptDelegate == null) {
|
1185 | 1210 | Log.e(
|
1186 | 1211 | LOGTAG, "Tried to install extension " + extension.id + " but no delegate is registered");
|
... | ... | @@ -1220,6 +1245,10 @@ public class WebExtensionController { |
1220 | 1245 | final WebExtension currentExtension =
|
1221 | 1246 | new WebExtension(mDelegateControllerProvider, currentBundle);
|
1222 | 1247 | |
1248 | + if (promptBypass(currentExtension, callback)) {
|
|
1249 | + return;
|
|
1250 | + }
|
|
1251 | + |
|
1223 | 1252 | final WebExtension updatedExtension =
|
1224 | 1253 | new WebExtension(mDelegateControllerProvider, updatedBundle);
|
1225 | 1254 |