ma1 pushed to branch base-browser-140.1.0esr-15.0-1 at The Tor Project / Applications / Tor Browser

Commits:

20 changed files:

Changes:

  • dom/media/MediaManager.cpp
    ... ... @@ -3568,7 +3568,9 @@ void MediaManager::OnCameraMute(bool aMute) {
    3568 3568
       mCamerasMuted = aMute;
    
    3569 3569
       // This is safe since we're on main-thread, and the windowlist can only
    
    3570 3570
       // be added to from the main-thread
    
    3571
    -  for (const auto& window : mActiveWindows.Values()) {
    
    3571
    +  for (const auto& window :
    
    3572
    +       ToTArray<AutoTArray<RefPtr<GetUserMediaWindowListener>, 2>>(
    
    3573
    +           mActiveWindows.Values())) {
    
    3572 3574
         window->MuteOrUnmuteCameras(aMute);
    
    3573 3575
       }
    
    3574 3576
     }
    
    ... ... @@ -3579,7 +3581,9 @@ void MediaManager::OnMicrophoneMute(bool aMute) {
    3579 3581
       mMicrophonesMuted = aMute;
    
    3580 3582
       // This is safe since we're on main-thread, and the windowlist can only
    
    3581 3583
       // be added to from the main-thread
    
    3582
    -  for (const auto& window : mActiveWindows.Values()) {
    
    3584
    +  for (const auto& window :
    
    3585
    +       ToTArray<AutoTArray<RefPtr<GetUserMediaWindowListener>, 2>>(
    
    3586
    +           mActiveWindows.Values())) {
    
    3583 3587
         window->MuteOrUnmuteMicrophones(aMute);
    
    3584 3588
       }
    
    3585 3589
     }
    
    ... ... @@ -4767,7 +4771,7 @@ void GetUserMediaWindowListener::MuteOrUnmuteCameras(bool aMute) {
    4767 4771
       }
    
    4768 4772
       mCamerasAreMuted = aMute;
    
    4769 4773
     
    
    4770
    -  for (auto& l : mActiveListeners) {
    
    4774
    +  for (auto& l : mActiveListeners.Clone()) {
    
    4771 4775
         if (l->GetDevice()->Kind() == MediaDeviceKind::Videoinput) {
    
    4772 4776
           l->MuteOrUnmuteCamera(aMute);
    
    4773 4777
         }
    
    ... ... @@ -4782,7 +4786,7 @@ void GetUserMediaWindowListener::MuteOrUnmuteMicrophones(bool aMute) {
    4782 4786
       }
    
    4783 4787
       mMicrophonesAreMuted = aMute;
    
    4784 4788
     
    
    4785
    -  for (auto& l : mActiveListeners) {
    
    4789
    +  for (auto& l : mActiveListeners.Clone()) {
    
    4786 4790
         if (l->GetDevice()->Kind() == MediaDeviceKind::Audioinput) {
    
    4787 4791
           l->MuteOrUnmuteMicrophone(aMute);
    
    4788 4792
         }
    

  • dom/security/nsContentSecurityUtils.cpp
    ... ... @@ -2206,11 +2206,17 @@ void nsContentSecurityUtils::LogMessageToConsole(nsIHttpChannel* aChannel,
    2206 2206
     }
    
    2207 2207
     
    
    2208 2208
     /* static */
    
    2209
    -long nsContentSecurityUtils::ClassifyDownload(
    
    2210
    -    nsIChannel* aChannel, const nsAutoCString& aMimeTypeGuess) {
    
    2209
    +long nsContentSecurityUtils::ClassifyDownload(nsIChannel* aChannel) {
    
    2211 2210
       MOZ_ASSERT(aChannel, "IsDownloadAllowed without channel?");
    
    2212 2211
     
    
    2213 2212
       nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    
    2213
    +  if ((loadInfo->GetTriggeringSandboxFlags() & SANDBOXED_ALLOW_DOWNLOADS) ||
    
    2214
    +      (loadInfo->GetSandboxFlags() & SANDBOXED_ALLOW_DOWNLOADS)) {
    
    2215
    +    if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
    
    2216
    +      LogMessageToConsole(httpChannel, "IframeSandboxBlockedDownload");
    
    2217
    +    }
    
    2218
    +    return nsITransfer::DOWNLOAD_FORBIDDEN;
    
    2219
    +  }
    
    2214 2220
     
    
    2215 2221
       nsCOMPtr<nsIURI> contentLocation;
    
    2216 2222
       aChannel->GetURI(getter_AddRefs(contentLocation));
    
    ... ... @@ -2243,27 +2249,11 @@ long nsContentSecurityUtils::ClassifyDownload(
    2243 2249
     
    
    2244 2250
       if (StaticPrefs::dom_block_download_insecure() &&
    
    2245 2251
           decission != nsIContentPolicy::ACCEPT) {
    
    2246
    -    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
    
    2247
    -    if (httpChannel) {
    
    2252
    +    if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
    
    2248 2253
           LogMessageToConsole(httpChannel, "MixedContentBlockedDownload");
    
    2249 2254
         }
    
    2250 2255
         return nsITransfer::DOWNLOAD_POTENTIALLY_UNSAFE;
    
    2251 2256
       }
    
    2252 2257
     
    
    2253
    -  if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
    
    2254
    -    return nsITransfer::DOWNLOAD_ACCEPTABLE;
    
    2255
    -  }
    
    2256
    -
    
    2257
    -  uint32_t triggeringFlags = loadInfo->GetTriggeringSandboxFlags();
    
    2258
    -  uint32_t currentflags = loadInfo->GetSandboxFlags();
    
    2259
    -
    
    2260
    -  if ((triggeringFlags & SANDBOXED_ALLOW_DOWNLOADS) ||
    
    2261
    -      (currentflags & SANDBOXED_ALLOW_DOWNLOADS)) {
    
    2262
    -    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
    
    2263
    -    if (httpChannel) {
    
    2264
    -      LogMessageToConsole(httpChannel, "IframeSandboxBlockedDownload");
    
    2265
    -    }
    
    2266
    -    return nsITransfer::DOWNLOAD_FORBIDDEN;
    
    2267
    -  }
    
    2268 2258
       return nsITransfer::DOWNLOAD_ACCEPTABLE;
    
    2269 2259
     }

  • dom/security/nsContentSecurityUtils.h
    ... ... @@ -74,8 +74,7 @@ class nsContentSecurityUtils {
    74 74
           const mozilla::dom::Element& aElement);
    
    75 75
     
    
    76 76
       // Helper function to Check if a Download is allowed;
    
    77
    -  static long ClassifyDownload(nsIChannel* aChannel,
    
    78
    -                               const nsAutoCString& aMimeTypeGuess);
    
    77
    +  static long ClassifyDownload(nsIChannel* aChannel);
    
    79 78
     
    
    80 79
       // Public only for testing
    
    81 80
       static FilenameTypeAndDetails FilenameToFilenameType(
    

  • dom/xslt/xpath/txXPathNode.h
    ... ... @@ -66,6 +66,8 @@ class txXPathNode {
    66 66
       bool operator!=(const txXPathNode& aNode) const { return !(*this == aNode); }
    
    67 67
       ~txXPathNode() { MOZ_COUNT_DTOR(txXPathNode); }
    
    68 68
     
    
    69
    +  mozilla::dom::Document* OwnerDoc() const { return mNode->OwnerDoc(); }
    
    70
    +
    
    69 71
      private:
    
    70 72
       friend class txXPathNativeNode;
    
    71 73
       friend class txXPathNodeUtils;
    

  • dom/xslt/xslt/txNodeSorter.cpp
    ... ... @@ -13,10 +13,13 @@
    13 13
     
    
    14 14
     #include "mozilla/CheckedInt.h"
    
    15 15
     #include "mozilla/UniquePtrExtensions.h"
    
    16
    +#include "nsRFPService.h"
    
    16 17
     
    
    17 18
     using mozilla::CheckedUint32;
    
    18 19
     using mozilla::MakeUnique;
    
    19 20
     using mozilla::MakeUniqueFallible;
    
    21
    +using mozilla::nsRFPService;
    
    22
    +using mozilla::RFPTarget;
    
    20 23
     using mozilla::UniquePtr;
    
    21 24
     
    
    22 25
     /*
    
    ... ... @@ -74,6 +77,10 @@ nsresult txNodeSorter::addSortElement(Expr* aSelectExpr, Expr* aLangExpr,
    74 77
         if (aLangExpr) {
    
    75 78
           rv = aLangExpr->evaluateToString(aContext, lang);
    
    76 79
           NS_ENSURE_SUCCESS(rv, rv);
    
    80
    +    } else if (aContext->getContextNode()
    
    81
    +                   .OwnerDoc()
    
    82
    +                   ->ShouldResistFingerprinting(RFPTarget::JSLocale)) {
    
    83
    +      CopyUTF8toUTF16(nsRFPService::GetSpoofedJSLocale(), lang);
    
    77 84
         }
    
    78 85
     
    
    79 86
         // Case-order
    

  • mobile/android/android-components/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/OriginView.kt
    ... ... @@ -7,6 +7,7 @@ package mozilla.components.browser.toolbar.display
    7 7
     import android.animation.LayoutTransition
    
    8 8
     import android.content.Context
    
    9 9
     import android.graphics.Typeface
    
    10
    +import android.text.Spanned
    
    10 11
     import android.util.AttributeSet
    
    11 12
     import android.util.TypedValue
    
    12 13
     import android.view.Gravity
    
    ... ... @@ -17,6 +18,7 @@ import androidx.annotation.VisibleForTesting
    17 18
     import androidx.core.view.isVisible
    
    18 19
     import mozilla.components.browser.toolbar.BrowserToolbar
    
    19 20
     import mozilla.components.browser.toolbar.R
    
    21
    +import mozilla.components.concept.toolbar.Toolbar
    
    20 22
     
    
    21 23
     /**
    
    22 24
      * View displaying the URL and optionally the title of a website.
    
    ... ... @@ -48,6 +50,9 @@ internal class OriginView @JvmOverloads constructor(
    48 50
             isClickable = true
    
    49 51
             isFocusable = true
    
    50 52
     
    
    53
    +        textDirection = View.TEXT_DIRECTION_LTR
    
    54
    +        layoutDirection = View.LAYOUT_DIRECTION_LTR
    
    55
    +
    
    51 56
             setOnClickListener {
    
    52 57
                 if (onUrlClicked()) {
    
    53 58
                     toolbar.editMode()
    
    ... ... @@ -134,9 +139,50 @@ internal class OriginView @JvmOverloads constructor(
    134 139
             titleView.setOnLongClickListener(handler)
    
    135 140
         }
    
    136 141
     
    
    142
    +    /**
    
    143
    +     * Scrolls the URL view to ensure the registrable domain is visible.
    
    144
    +     */
    
    145
    +    @VisibleForTesting
    
    146
    +    internal fun scrollToShowRegistrableDomain() {
    
    147
    +        val text = urlView.text
    
    148
    +
    
    149
    +        val spans = (text as? Spanned)?.getSpans(
    
    150
    +            0,
    
    151
    +            text.length,
    
    152
    +            Toolbar.RegistrableDomainColorSpan::class.java,
    
    153
    +        )
    
    154
    +
    
    155
    +        if (spans?.size == 1) {
    
    156
    +            val registrableDomainSpan = (urlView.text as? Spanned)?.getSpans(
    
    157
    +                0,
    
    158
    +                text.length,
    
    159
    +                Toolbar.RegistrableDomainColorSpan::class.java,
    
    160
    +            )?.getOrNull(0)
    
    161
    +
    
    162
    +            val valueUntilRegistrableDomainEnd = text.subSequence(0, text.getSpanEnd(registrableDomainSpan))
    
    163
    +
    
    164
    +            val urlViewWidth = urlView.width
    
    165
    +            val valueWidth = measureUrlTextWidh(valueUntilRegistrableDomainEnd.toString())
    
    166
    +
    
    167
    +            if (valueWidth > urlViewWidth) {
    
    168
    +                urlView.scrollTo((valueWidth - urlViewWidth).toInt(), 0)
    
    169
    +                return
    
    170
    +            }
    
    171
    +        }
    
    172
    +
    
    173
    +        urlView.scrollTo(0, 0)
    
    174
    +    }
    
    175
    +
    
    176
    +    @VisibleForTesting
    
    177
    +    internal fun measureUrlTextWidh(text: String) = urlView.paint.measureText(text)
    
    178
    +
    
    137 179
         internal var url: CharSequence
    
    138 180
             get() = urlView.text
    
    139
    -        set(value) { urlView.text = value }
    
    181
    +        set(value) {
    
    182
    +            urlView.text = value
    
    183
    +
    
    184
    +            scrollToShowRegistrableDomain()
    
    185
    +        }
    
    140 186
     
    
    141 187
         /**
    
    142 188
          * Sets the colour of the text to be displayed when the URL of the toolbar is empty.
    

  • mobile/android/android-components/components/browser/toolbar/src/test/java/mozilla/components/browser/toolbar/display/OriginViewTest.kt
    1
    +/* This Source Code Form is subject to the terms of the Mozilla Public
    
    2
    + * License, v. 2.0. If a copy of the MPL was not distributed with this
    
    3
    + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
    
    4
    +
    
    5
    +package mozilla.components.browser.toolbar.display
    
    6
    +
    
    7
    +import android.graphics.Color
    
    8
    +import android.text.SpannableStringBuilder
    
    9
    +import android.text.SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE
    
    10
    +import android.text.style.ForegroundColorSpan
    
    11
    +import android.view.View
    
    12
    +import androidx.annotation.ColorInt
    
    13
    +import androidx.test.ext.junit.runners.AndroidJUnit4
    
    14
    +import mozilla.components.concept.toolbar.Toolbar
    
    15
    +import mozilla.components.support.test.any
    
    16
    +import mozilla.components.support.test.robolectric.testContext
    
    17
    +import org.junit.Assert.assertEquals
    
    18
    +import org.junit.Test
    
    19
    +import org.junit.runner.RunWith
    
    20
    +import org.mockito.Mockito.doReturn
    
    21
    +import org.mockito.Mockito.spy
    
    22
    +
    
    23
    +@RunWith(AndroidJUnit4::class)
    
    24
    +class OriginViewTest {
    
    25
    +
    
    26
    +    private fun SpannableStringBuilder.applyUrlColors(
    
    27
    +        @ColorInt urlColor: Int,
    
    28
    +        @ColorInt registrableDomainColor: Int,
    
    29
    +        registrableDomainOrHostSpan: Pair<Int, Int>,
    
    30
    +    ): SpannableStringBuilder = apply {
    
    31
    +        setSpan(
    
    32
    +            ForegroundColorSpan(urlColor),
    
    33
    +            0,
    
    34
    +            length,
    
    35
    +            SPAN_INCLUSIVE_INCLUSIVE,
    
    36
    +        )
    
    37
    +
    
    38
    +        val (start, end) = registrableDomainOrHostSpan
    
    39
    +        setSpan(
    
    40
    +            Toolbar.RegistrableDomainColorSpan(registrableDomainColor),
    
    41
    +            start,
    
    42
    +            end,
    
    43
    +            SPAN_INCLUSIVE_INCLUSIVE,
    
    44
    +        )
    
    45
    +    }
    
    46
    +
    
    47
    +    @Test
    
    48
    +    fun `scrollToShowRegistrableDomain scrolls when domain exceeds width`() {
    
    49
    +        val view = spy(OriginView(testContext))
    
    50
    +        val url = "https://www.really-long-example-domain.com/"
    
    51
    +        val spannedUrl = SpannableStringBuilder(url).apply {
    
    52
    +            applyUrlColors(
    
    53
    +                urlColor = Color.GREEN,
    
    54
    +                registrableDomainColor = Color.RED,
    
    55
    +                registrableDomainOrHostSpan = 8 to 42,
    
    56
    +            )
    
    57
    +        }
    
    58
    +
    
    59
    +        // Long domain wouldn't fit in the view
    
    60
    +        doReturn(500f).`when`(view).measureUrlTextWidh(any())
    
    61
    +        view.urlView.layout(0, 0, 200, 100)
    
    62
    +
    
    63
    +        view.url = spannedUrl
    
    64
    +
    
    65
    +        assertEquals(300, view.urlView.scrollX)
    
    66
    +    }
    
    67
    +
    
    68
    +    @Test
    
    69
    +    fun `scrollToShowRegistrableDomain does not scroll when domain fits in view`() {
    
    70
    +        val view = spy(OriginView(testContext))
    
    71
    +        val url = "https://mozilla.org/"
    
    72
    +        val spannedUrl = SpannableStringBuilder(url).apply {
    
    73
    +            applyUrlColors(
    
    74
    +                urlColor = Color.GREEN,
    
    75
    +                registrableDomainColor = Color.RED,
    
    76
    +                registrableDomainOrHostSpan = 8 to 19,
    
    77
    +            )
    
    78
    +        }
    
    79
    +
    
    80
    +        doReturn(50f).`when`(view).measureUrlTextWidh(any())
    
    81
    +        view.urlView.layout(0, 0, 200, 100)
    
    82
    +
    
    83
    +        view.url = spannedUrl
    
    84
    +
    
    85
    +        assertEquals(0, view.urlView.scrollX)
    
    86
    +    }
    
    87
    +
    
    88
    +    @Test
    
    89
    +    fun `scrollToShowRegistrableDomain does not scroll when no span exists`() {
    
    90
    +        val view = OriginView(testContext)
    
    91
    +
    
    92
    +        val spanned = SpannableStringBuilder("nospan.com") // no span set
    
    93
    +
    
    94
    +        view.measure(0, 0)
    
    95
    +        view.layout(0, 0, 500, 100)
    
    96
    +
    
    97
    +        view.url = spanned
    
    98
    +
    
    99
    +        assertEquals(0, view.urlView.scrollX)
    
    100
    +    }
    
    101
    +
    
    102
    +    @Test
    
    103
    +    fun `URL text direction is always LTR`() {
    
    104
    +        val originView = OriginView(testContext)
    
    105
    +        originView.url = "ختار.ار/www.mozilla.org/1"
    
    106
    +        assertEquals(View.TEXT_DIRECTION_LTR, originView.urlView.textDirection)
    
    107
    +        assertEquals(View.LAYOUT_DIRECTION_LTR, originView.urlView.layoutDirection)
    
    108
    +    }
    
    109
    +}

  • mobile/android/android-components/components/concept/toolbar/src/main/java/mozilla/components/concept/toolbar/Toolbar.kt
    ... ... @@ -5,11 +5,13 @@
    5 5
     package mozilla.components.concept.toolbar
    
    6 6
     
    
    7 7
     import android.graphics.drawable.Drawable
    
    8
    +import android.text.style.ForegroundColorSpan
    
    8 9
     import android.view.View
    
    9 10
     import android.view.View.NO_ID
    
    10 11
     import android.view.ViewGroup
    
    11 12
     import android.widget.ImageButton
    
    12 13
     import android.widget.ImageView
    
    14
    +import androidx.annotation.ColorInt
    
    13 15
     import androidx.annotation.ColorRes
    
    14 16
     import androidx.annotation.Dimension
    
    15 17
     import androidx.annotation.Dimension.Companion.DP
    
    ... ... @@ -548,6 +550,13 @@ interface Toolbar : ScrollableToolbar {
    548 550
              */
    
    549 551
             END,
    
    550 552
         }
    
    553
    +
    
    554
    +    /**
    
    555
    +     * Registrable domain foreground color span.
    
    556
    +     *
    
    557
    +     * This simple class extension is used so that we can filter for it elsewhere.
    
    558
    +     */
    
    559
    +    class RegistrableDomainColorSpan(@ColorInt color: Int) : ForegroundColorSpan(color)
    
    551 560
     }
    
    552 561
     
    
    553 562
     private fun AppCompatImageButton.setTintResource(@ColorRes tintColorResource: Int) {
    

  • mobile/android/android-components/components/feature/toolbar/src/main/java/mozilla/components/feature/toolbar/ToolbarFeature.kt
    ... ... @@ -88,11 +88,13 @@ class ToolbarFeature(
    88 88
         )
    
    89 89
     
    
    90 90
         /**
    
    91
    -     * Controls how the url should be styled
    
    91
    +     * Controls how the URL should be styled
    
    92 92
          *
    
    93 93
          * RegistrableDomain: displays only the eTLD+1 (direct subdomain of the public suffix), uncolored
    
    94
    -     * ColoredUrl: displays the registrableDomain with color and url with another color
    
    95
    -     * UncoloredUrl: displays the full url, uncolored
    
    94
    +     * ColoredUrl: displays the full URL with distinct colors for the registrable domain and the rest of the URL.
    
    95
    +     *   Colors the entire hostname if the registrable domain cannot be determined or is an IP address.
    
    96
    +     *   Leaves non http(s) URLs uncolored.
    
    97
    +     * UncoloredUrl: displays the full URL, uncolored
    
    96 98
          */
    
    97 99
         sealed class RenderStyle {
    
    98 100
             object RegistrableDomain : RenderStyle()
    

  • mobile/android/android-components/components/feature/toolbar/src/main/java/mozilla/components/feature/toolbar/internal/URLRenderer.kt
    ... ... @@ -18,6 +18,11 @@ import kotlinx.coroutines.channels.trySendBlocking
    18 18
     import kotlinx.coroutines.launch
    
    19 19
     import mozilla.components.concept.toolbar.Toolbar
    
    20 20
     import mozilla.components.feature.toolbar.ToolbarFeature
    
    21
    +import mozilla.components.lib.publicsuffixlist.PublicSuffixList
    
    22
    +import mozilla.components.support.ktx.android.net.isHttpOrHttps
    
    23
    +import mozilla.components.support.ktx.kotlin.isIpv4OrIpv6
    
    24
    +
    
    25
    +private const val BLOB_URL_PREFIX = "blob:"
    
    21 26
     
    
    22 27
     /**
    
    23 28
      * Asynchronous URL renderer.
    
    ... ... @@ -73,13 +78,21 @@ internal class URLRenderer(
    73 78
             toolbar.url = when (configuration.renderStyle) {
    
    74 79
                 // Display only the eTLD+1 (direct subdomain of the public suffix), uncolored
    
    75 80
                 ToolbarFeature.RenderStyle.RegistrableDomain -> {
    
    76
    -                val host = url.toUri().host?.ifEmpty { null }
    
    77
    -                host?.let { getRegistrableDomain(host, configuration) } ?: url
    
    81
    +                getRegistrableDomainOrHostSpan(url, configuration.publicSuffixList)?.let { (start, end) ->
    
    82
    +                    url.substring(start, end)
    
    83
    +                } ?: url
    
    78 84
                 }
    
    79 85
                 // Display the registrableDomain with color and URL with another color
    
    80 86
                 ToolbarFeature.RenderStyle.ColoredUrl -> SpannableStringBuilder(url).apply {
    
    81
    -                color(configuration.urlColor)
    
    82
    -                colorRegistrableDomain(configuration)
    
    87
    +                val span = getRegistrableDomainOrHostSpan(url, configuration.publicSuffixList)
    
    88
    +
    
    89
    +                if (configuration.urlColor != null && span != null) {
    
    90
    +                    applyUrlColors(
    
    91
    +                        configuration.urlColor,
    
    92
    +                        configuration.registrableDomainColor,
    
    93
    +                        span,
    
    94
    +                    )
    
    95
    +                }
    
    83 96
                 }
    
    84 97
                 // Display the full URL, uncolored
    
    85 98
                 ToolbarFeature.RenderStyle.UncoloredUrl -> url
    
    ... ... @@ -87,43 +100,98 @@ internal class URLRenderer(
    87 100
         }
    
    88 101
     }
    
    89 102
     
    
    90
    -private suspend fun getRegistrableDomain(host: String, configuration: ToolbarFeature.UrlRenderConfiguration) =
    
    91
    -    configuration.publicSuffixList.getPublicSuffixPlusOne(host).await()
    
    103
    +/**
    
    104
    + * Determines the position span of the registrable domain within a host string.
    
    105
    + *
    
    106
    + * @param host The host string to analyze
    
    107
    + * @param publicSuffixList The [PublicSuffixList] used to get the eTLD+1 for the host
    
    108
    + * @return A Pair of (startIndex, endIndex) for the registrable domain within the host,
    
    109
    + *         or null if the host is an IP address or no registrable domain could be found
    
    110
    + */
    
    111
    +@VisibleForTesting
    
    112
    +internal suspend fun getRegistrableDomainSpanInHost(
    
    113
    +    host: String,
    
    114
    +    publicSuffixList: PublicSuffixList,
    
    115
    +): Pair<Int, Int>? {
    
    116
    +    if (host.isIpv4OrIpv6()) return null
    
    117
    +
    
    118
    +    val normalizedHost = host.removeSuffix(".")
    
    119
    +
    
    120
    +    val registrableDomain = publicSuffixList
    
    121
    +        .getPublicSuffixPlusOne(normalizedHost)
    
    122
    +        .await() ?: return null
    
    123
    +
    
    124
    +    val start = normalizedHost.lastIndexOf(registrableDomain)
    
    125
    +    return if (start == -1) {
    
    126
    +        null
    
    127
    +    } else {
    
    128
    +        start to start + registrableDomain.length
    
    129
    +    }
    
    130
    +}
    
    92 131
     
    
    93
    -private suspend fun SpannableStringBuilder.colorRegistrableDomain(
    
    94
    -    configuration: ToolbarFeature.UrlRenderConfiguration,
    
    95
    -) {
    
    96
    -    val url = toString()
    
    97
    -    val host = url.toUri().host?.removeSuffix(".") ?: return
    
    98
    -
    
    99
    -    val registrableDomain = configuration
    
    100
    -        .publicSuffixList
    
    101
    -        .getPublicSuffixPlusOne(host)
    
    102
    -        .await() ?: return
    
    103
    -
    
    104
    -    val indexOfHost = url.indexOf(host)
    
    105
    -    val indexOfRegistrableDomain = host.lastIndexOf(registrableDomain)
    
    106
    -    if (indexOfHost == -1 || indexOfRegistrableDomain == -1) {
    
    107
    -        return
    
    132
    +/**
    
    133
    + * Determines the position span of either the registrable domain or the full host
    
    134
    + * within a URL string.
    
    135
    + *
    
    136
    + * @param url The complete URL to analyze
    
    137
    + * @param publicSuffixList The [PublicSuffixList] used to get the eTLD+1 for the host
    
    138
    + * @param allowBlobUnwrapping Whether to allow unwrapping blob URLs
    
    139
    + * @return A Pair of (startIndex, endIndex) for either:
    
    140
    + *         - The registrable domain's position within the URL, or
    
    141
    + *         - The host's position within the URL if no registrable domain was found, or
    
    142
    + *         - null if the URL has no host or the host couldn't be located in the URL
    
    143
    + */
    
    144
    +@Suppress("ReturnCount")
    
    145
    +@VisibleForTesting
    
    146
    +internal suspend fun getRegistrableDomainOrHostSpan(
    
    147
    +    url: String,
    
    148
    +    publicSuffixList: PublicSuffixList,
    
    149
    +    allowBlobUnwrapping: Boolean = true,
    
    150
    +): Pair<Int, Int>? {
    
    151
    +    if (url.startsWith(BLOB_URL_PREFIX)) {
    
    152
    +        if (!allowBlobUnwrapping) return null
    
    153
    +
    
    154
    +        val innerUrl = url.substring(BLOB_URL_PREFIX.length)
    
    155
    +        return getRegistrableDomainOrHostSpan(
    
    156
    +            innerUrl,
    
    157
    +            publicSuffixList,
    
    158
    +            allowBlobUnwrapping = false,
    
    159
    +        )?.let { (start, end) ->
    
    160
    +            BLOB_URL_PREFIX.length + start to BLOB_URL_PREFIX.length + end
    
    161
    +        }
    
    108 162
         }
    
    109 163
     
    
    110
    -    val index = indexOfHost + indexOfRegistrableDomain
    
    164
    +    val uri = url.toUri()
    
    165
    +    if (!uri.isHttpOrHttps) return null
    
    111 166
     
    
    112
    -    setSpan(
    
    113
    -        ForegroundColorSpan(configuration.registrableDomainColor),
    
    114
    -        index,
    
    115
    -        index + registrableDomain.length,
    
    116
    -        SPAN_INCLUSIVE_INCLUSIVE,
    
    117
    -    )
    
    118
    -}
    
    167
    +    val host = uri.host ?: return null
    
    168
    +
    
    169
    +    val hostStart = url.indexOf(host)
    
    170
    +    if (hostStart == -1) return null
    
    119 171
     
    
    120
    -private fun SpannableStringBuilder.color(@ColorInt urlColor: Int?) {
    
    121
    -    urlColor ?: return
    
    172
    +    val domainSpan = getRegistrableDomainSpanInHost(host, publicSuffixList)
    
    173
    +    return domainSpan?.let { (start, end) ->
    
    174
    +        hostStart + start to hostStart + end
    
    175
    +    } ?: (hostStart to hostStart + host.length)
    
    176
    +}
    
    122 177
     
    
    178
    +private fun SpannableStringBuilder.applyUrlColors(
    
    179
    +    @ColorInt urlColor: Int,
    
    180
    +    @ColorInt registrableDomainColor: Int,
    
    181
    +    registrableDomainOrHostSpan: Pair<Int, Int>,
    
    182
    +): SpannableStringBuilder = apply {
    
    123 183
         setSpan(
    
    124 184
             ForegroundColorSpan(urlColor),
    
    125 185
             0,
    
    126 186
             length,
    
    127 187
             SPAN_INCLUSIVE_INCLUSIVE,
    
    128 188
         )
    
    189
    +
    
    190
    +    val (start, end) = registrableDomainOrHostSpan
    
    191
    +    setSpan(
    
    192
    +        Toolbar.RegistrableDomainColorSpan(registrableDomainColor),
    
    193
    +        start,
    
    194
    +        end,
    
    195
    +        SPAN_INCLUSIVE_INCLUSIVE,
    
    196
    +    )
    
    129 197
     }

  • mobile/android/android-components/components/feature/toolbar/src/test/java/mozilla/components/feature/toolbar/internal/URLRendererTest.kt
    ... ... @@ -5,8 +5,10 @@
    5 5
     package mozilla.components.feature.toolbar.internal
    
    6 6
     
    
    7 7
     import android.graphics.Color
    
    8
    +import android.net.InetAddresses
    
    8 9
     import android.text.SpannableStringBuilder
    
    9 10
     import android.text.style.ForegroundColorSpan
    
    11
    +import android.util.Patterns
    
    10 12
     import androidx.test.ext.junit.runners.AndroidJUnit4
    
    11 13
     import kotlinx.coroutines.Dispatchers
    
    12 14
     import mozilla.components.concept.toolbar.Toolbar
    
    ... ... @@ -26,8 +28,12 @@ import org.junit.Rule
    26 28
     import org.junit.Test
    
    27 29
     import org.junit.runner.RunWith
    
    28 30
     import org.mockito.Mockito.verify
    
    31
    +import org.robolectric.annotation.Config
    
    32
    +import org.robolectric.annotation.Implementation
    
    33
    +import org.robolectric.annotation.Implements
    
    29 34
     
    
    30 35
     @RunWith(AndroidJUnit4::class)
    
    36
    +@Config(shadows = [ShadowInetAddresses::class])
    
    31 37
     class URLRendererTest {
    
    32 38
     
    
    33 39
         @get:Rule
    
    ... ... @@ -104,10 +110,7 @@ class URLRendererTest {
    104 110
             }
    
    105 111
         }
    
    106 112
     
    
    107
    -    private suspend fun testRenderWithColoredUrl(
    
    108
    -        testUrl: String,
    
    109
    -        expectedRegistrableDomainSpan: Pair<Int, Int>,
    
    110
    -    ) {
    
    113
    +    private suspend fun getSpannedUrl(testUrl: String): SpannableStringBuilder {
    
    111 114
             val configuration = ToolbarFeature.UrlRenderConfiguration(
    
    112 115
                 publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    113 116
                 registrableDomainColor = Color.RED,
    
    ... ... @@ -124,9 +127,14 @@ class URLRendererTest {
    124 127
             val captor = argumentCaptor<CharSequence>()
    
    125 128
             verify(toolbar).url = captor.capture()
    
    126 129
     
    
    127
    -        assertNotNull(captor.value)
    
    128
    -        assertTrue(captor.value is SpannableStringBuilder)
    
    129
    -        val url = captor.value as SpannableStringBuilder
    
    130
    +        return requireNotNull(captor.value as? SpannableStringBuilder) { "Toolbar URL should not be null" }
    
    131
    +    }
    
    132
    +
    
    133
    +    private suspend fun testRenderWithColoredUrl(
    
    134
    +        testUrl: String,
    
    135
    +        expectedRegistrableDomainSpan: Pair<Int, Int>,
    
    136
    +    ) {
    
    137
    +        val url = getSpannedUrl(testUrl)
    
    130 138
     
    
    131 139
             assertEquals(testUrl, url.toString())
    
    132 140
     
    
    ... ... @@ -143,8 +151,237 @@ class URLRendererTest {
    143 151
             assertEquals(expectedRegistrableDomainSpan.second, url.getSpanEnd(spans[1]))
    
    144 152
         }
    
    145 153
     
    
    154
    +    private suspend fun testRenderWithUncoloredUrl(testUrl: String) {
    
    155
    +        val url = getSpannedUrl(testUrl)
    
    156
    +
    
    157
    +        assertEquals(testUrl, url.toString())
    
    158
    +
    
    159
    +        val spans = url.getSpans(0, url.length, ForegroundColorSpan::class.java)
    
    160
    +
    
    161
    +        assertEquals(0, spans.size)
    
    162
    +    }
    
    163
    +
    
    164
    +    private suspend fun testRenderWithRegistrableDomain(
    
    165
    +        testUrl: String,
    
    166
    +        expectedUrl: String,
    
    167
    +    ) {
    
    168
    +        val configuration = ToolbarFeature.UrlRenderConfiguration(
    
    169
    +            publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    170
    +            registrableDomainColor = Color.RED,
    
    171
    +            urlColor = Color.GREEN,
    
    172
    +            renderStyle = ToolbarFeature.RenderStyle.RegistrableDomain,
    
    173
    +        )
    
    174
    +
    
    175
    +        val toolbar: Toolbar = mock()
    
    176
    +
    
    177
    +        val renderer = URLRenderer(toolbar, configuration)
    
    178
    +
    
    179
    +        renderer.updateUrl(testUrl)
    
    180
    +
    
    181
    +        val captor = argumentCaptor<CharSequence>()
    
    182
    +        verify(toolbar).url = captor.capture()
    
    183
    +
    
    184
    +        assertNotNull(captor.value)
    
    185
    +        assertTrue(captor.value is String)
    
    186
    +        val url = captor.value as String
    
    187
    +
    
    188
    +        assertEquals(expectedUrl, url)
    
    189
    +    }
    
    190
    +
    
    191
    +    @Test
    
    192
    +    fun `GIVEN a simple domain WHEN getting registrable domain span in host THEN span is returned`() {
    
    193
    +        runTestOnMain {
    
    194
    +            val domainSpan = getRegistrableDomainSpanInHost(
    
    195
    +                host = "www.mozilla.org",
    
    196
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    197
    +            )
    
    198
    +
    
    199
    +            assertEquals(4 to 15, domainSpan)
    
    200
    +        }
    
    201
    +    }
    
    202
    +
    
    203
    +    @Test
    
    204
    +    fun `GIVEN a host with a trailing period in the domain WHEN getting registrable domain span in host THEN span is returned`() {
    
    205
    +        runTestOnMain {
    
    206
    +            val domainSpan = getRegistrableDomainSpanInHost(
    
    207
    +                host = "www.mozilla.org.",
    
    208
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    209
    +            )
    
    210
    +
    
    211
    +            assertEquals(4 to 15, domainSpan)
    
    212
    +        }
    
    213
    +    }
    
    214
    +
    
    215
    +    @Test
    
    216
    +    fun `GIVEN a host with a repeated domain WHEN getting registrable domain span in host THEN the span of the last occurrence of domain is returned`() {
    
    217
    +        runTestOnMain {
    
    218
    +            val domainSpan = getRegistrableDomainSpanInHost(
    
    219
    +                host = "mozilla.org.mozilla.org",
    
    220
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    221
    +            )
    
    222
    +
    
    223
    +            assertEquals(12 to 23, domainSpan)
    
    224
    +        }
    
    225
    +    }
    
    226
    +
    
    227
    +    @Test
    
    228
    +    fun `GIVEN an IPv4 address as host WHEN getting registrable domain span in host THEN null is returned`() {
    
    229
    +        runTestOnMain {
    
    230
    +            val domainSpan = getRegistrableDomainSpanInHost(
    
    231
    +                host = "127.0.0.1",
    
    232
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    233
    +            )
    
    234
    +
    
    235
    +            assertNull(domainSpan)
    
    236
    +        }
    
    237
    +    }
    
    238
    +
    
    239
    +    @Test
    
    240
    +    fun `GIVEN an IPv6 address as host WHEN getting registrable domain span in host THEN null is returned`() {
    
    241
    +        runTestOnMain {
    
    242
    +            val domainSpan = getRegistrableDomainSpanInHost(
    
    243
    +                host = "[::1]",
    
    244
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    245
    +            )
    
    246
    +
    
    247
    +            assertNull(domainSpan)
    
    248
    +        }
    
    249
    +    }
    
    250
    +
    
    251
    +    @Test
    
    252
    +    fun `GIVEN a non PSL domain as host WHEN getting registrable domain span in host THEN null is returned`() {
    
    253
    +        runTestOnMain {
    
    254
    +            val domainSpan = getRegistrableDomainSpanInHost(
    
    255
    +                host = "localhost",
    
    256
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    257
    +            )
    
    258
    +
    
    259
    +            assertNull(domainSpan)
    
    260
    +        }
    
    261
    +    }
    
    262
    +
    
    263
    +    @Test
    
    264
    +    fun `GIVEN a simple URL WHEN getting registrable domain or host span THEN span is returned`() {
    
    265
    +        runTestOnMain {
    
    266
    +            val span = getRegistrableDomainOrHostSpan(
    
    267
    +                url = "https://www.mozilla.org/",
    
    268
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    269
    +            )
    
    270
    +
    
    271
    +            assertEquals(12 to 23, span)
    
    272
    +        }
    
    273
    +    }
    
    274
    +
    
    275
    +    @Test
    
    276
    +    fun `GIVEN a URL with a trailing period in the domain WHEN getting registrable domain or host span THEN span is returned`() {
    
    277
    +        runTestOnMain {
    
    278
    +            val span = getRegistrableDomainOrHostSpan(
    
    279
    +                url = "https://www.mozilla.org./",
    
    280
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    281
    +            )
    
    282
    +
    
    283
    +            assertEquals(12 to 23, span)
    
    284
    +        }
    
    285
    +    }
    
    286
    +
    
    287
    +    @Test
    
    288
    +    fun `GIVEN a URL with a repeated domain WHEN getting registrable domain or host span THEN the span of the last occurrence of domain is returned`() {
    
    289
    +        runTestOnMain {
    
    290
    +            val span = getRegistrableDomainOrHostSpan(
    
    291
    +                url = "https://mozilla.org.mozilla.org/",
    
    292
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    293
    +            )
    
    294
    +
    
    295
    +            assertEquals(20 to 31, span)
    
    296
    +        }
    
    297
    +    }
    
    298
    +
    
    299
    +    @Test
    
    300
    +    fun `GIVEN a URL with an IPv4 address WHEN getting registrable domain or host span THEN the span of the IP part is returned`() {
    
    301
    +        runTestOnMain {
    
    302
    +            val span = getRegistrableDomainOrHostSpan(
    
    303
    +                url = "http://127.0.0.1/",
    
    304
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    305
    +            )
    
    306
    +
    
    307
    +            assertEquals(7 to 16, span)
    
    308
    +        }
    
    309
    +    }
    
    310
    +
    
    311
    +    @Test
    
    312
    +    fun `GIVEN a URL with an IPv6 address WHEN getting registrable domain or host span THEN the span of the IP part is returned`() {
    
    313
    +        runTestOnMain {
    
    314
    +            val span = getRegistrableDomainOrHostSpan(
    
    315
    +                url = "http://[::1]/",
    
    316
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    317
    +            )
    
    318
    +
    
    319
    +            assertEquals(7 to 12, span)
    
    320
    +        }
    
    321
    +    }
    
    322
    +
    
    146 323
         @Test
    
    147
    -    fun `Render with simple URL`() {
    
    324
    +    fun `GIVEN a URL with a non PSL domain WHEN getting registrable domain or host span THEN the span of the host part is returned`() {
    
    325
    +        runTestOnMain {
    
    326
    +            val span = getRegistrableDomainOrHostSpan(
    
    327
    +                url = "http://localhost/",
    
    328
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    329
    +            )
    
    330
    +
    
    331
    +            assertEquals(7 to 16, span)
    
    332
    +        }
    
    333
    +    }
    
    334
    +
    
    335
    +    @Test
    
    336
    +    fun `GIVEN an internal page name WHEN getting registrable domain or host span THEN null is returned`() {
    
    337
    +        runTestOnMain {
    
    338
    +            val span = getRegistrableDomainOrHostSpan(
    
    339
    +                url = "about:mozilla",
    
    340
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    341
    +            )
    
    342
    +
    
    343
    +            assertNull(span)
    
    344
    +        }
    
    345
    +    }
    
    346
    +
    
    347
    +    @Test
    
    348
    +    fun `GIVEN a content URI WHEN getting registrable domain or host span THEN null is returned`() {
    
    349
    +        runTestOnMain {
    
    350
    +            val span = getRegistrableDomainOrHostSpan(
    
    351
    +                url = "content://media/external/file/1000000000",
    
    352
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    353
    +            )
    
    354
    +
    
    355
    +            assertNull(span)
    
    356
    +        }
    
    357
    +    }
    
    358
    +
    
    359
    +    @Test
    
    360
    +    fun `GIVEN a blob URI WHEN getting registrable domain or host span THEN domain span is returned`() {
    
    361
    +        runTestOnMain {
    
    362
    +            val span = getRegistrableDomainOrHostSpan(
    
    363
    +                url = "blob:https://www.mozilla.org/69a29afb-938c-4b9e-9fca-b2f79755047a",
    
    364
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    365
    +            )
    
    366
    +
    
    367
    +            assertEquals(17 to 28, span)
    
    368
    +        }
    
    369
    +    }
    
    370
    +
    
    371
    +    @Test
    
    372
    +    fun `GIVEN a blob URI with duplicated blob prefix WHEN getting registrable domain or host span THEN null is returned`() {
    
    373
    +        runTestOnMain {
    
    374
    +            val span = getRegistrableDomainOrHostSpan(
    
    375
    +                url = "blob:blob:https://www.mozilla.org/69a29afb-938c-4b9e-9fca-b2f79755047a",
    
    376
    +                publicSuffixList = PublicSuffixList(testContext, Dispatchers.Unconfined),
    
    377
    +            )
    
    378
    +
    
    379
    +            assertNull(span)
    
    380
    +        }
    
    381
    +    }
    
    382
    +
    
    383
    +    @Test
    
    384
    +    fun `GIVEN a simple URL WHEN rendering it THEN registrable domain is colored`() {
    
    148 385
             runTestOnMain {
    
    149 386
                 testRenderWithColoredUrl(
    
    150 387
                     testUrl = "https://www.mozilla.org/",
    
    ... ... @@ -154,7 +391,7 @@ class URLRendererTest {
    154 391
         }
    
    155 392
     
    
    156 393
         @Test
    
    157
    -    fun `Render with URL containing domain with trailing period`() {
    
    394
    +    fun `GIVEN a URL with a trailing period in the domain WHEN rendering it THEN registrable domain is colored`() {
    
    158 395
             runTestOnMain {
    
    159 396
                 testRenderWithColoredUrl(
    
    160 397
                     testUrl = "https://www.mozilla.org./",
    
    ... ... @@ -164,7 +401,7 @@ class URLRendererTest {
    164 401
         }
    
    165 402
     
    
    166 403
         @Test
    
    167
    -    fun `Render with URL containing repeated domain`() {
    
    404
    +    fun `GIVEN a URL with a repeated domain WHEN rendering it THEN the last occurrence of domain is colored`() {
    
    168 405
             runTestOnMain {
    
    169 406
                 testRenderWithColoredUrl(
    
    170 407
                     testUrl = "https://mozilla.org.mozilla.org/",
    
    ... ... @@ -172,4 +409,144 @@ class URLRendererTest {
    172 409
                 )
    
    173 410
             }
    
    174 411
         }
    
    412
    +
    
    413
    +    @Test
    
    414
    +    fun `GIVEN a URL with an IPv4 address WHEN rendering it THEN the IP part is colored`() {
    
    415
    +        runTestOnMain {
    
    416
    +            testRenderWithColoredUrl(
    
    417
    +                testUrl = "http://127.0.0.1/",
    
    418
    +                expectedRegistrableDomainSpan = 7 to 16,
    
    419
    +            )
    
    420
    +        }
    
    421
    +    }
    
    422
    +
    
    423
    +    @Test
    
    424
    +    fun `GIVEN a URL with an IPv6 address WHEN rendering it THEN the IP part is colored`() {
    
    425
    +        runTestOnMain {
    
    426
    +            testRenderWithColoredUrl(
    
    427
    +                testUrl = "http://[::1]/",
    
    428
    +                expectedRegistrableDomainSpan = 7 to 12,
    
    429
    +            )
    
    430
    +        }
    
    431
    +    }
    
    432
    +
    
    433
    +    @Test
    
    434
    +    fun `GIVEN a URL with a non PSL domain WHEN rendering it THEN host colored`() {
    
    435
    +        runTestOnMain {
    
    436
    +            testRenderWithColoredUrl(
    
    437
    +                testUrl = "http://localhost/",
    
    438
    +                expectedRegistrableDomainSpan = 7 to 16,
    
    439
    +            )
    
    440
    +        }
    
    441
    +    }
    
    442
    +
    
    443
    +    @Test
    
    444
    +    fun `GIVEN an internal page name WHEN rendering it THEN nothing is colored`() {
    
    445
    +        runTestOnMain {
    
    446
    +            testRenderWithUncoloredUrl("about:mozilla")
    
    447
    +        }
    
    448
    +    }
    
    449
    +
    
    450
    +    @Test
    
    451
    +    fun `GIVEN a content URI WHEN rendering it THEN nothing is colored`() {
    
    452
    +        runTestOnMain {
    
    453
    +            testRenderWithUncoloredUrl("content://media/external/file/1000000000")
    
    454
    +        }
    
    455
    +    }
    
    456
    +
    
    457
    +    @Test
    
    458
    +    fun `GIVEN a simple URL WHEN rendering it THEN registrable domain is set`() {
    
    459
    +        runTestOnMain {
    
    460
    +            testRenderWithRegistrableDomain(
    
    461
    +                testUrl = "https://www.mozilla.org/",
    
    462
    +                expectedUrl = "mozilla.org",
    
    463
    +            )
    
    464
    +        }
    
    465
    +    }
    
    466
    +
    
    467
    +    @Test
    
    468
    +    fun `GIVEN a URL with a trailing period in the domain WHEN rendering it THEN registrable domain is set`() {
    
    469
    +        runTestOnMain {
    
    470
    +            testRenderWithRegistrableDomain(
    
    471
    +                testUrl = "https://www.mozilla.org./",
    
    472
    +                expectedUrl = "mozilla.org",
    
    473
    +            )
    
    474
    +        }
    
    475
    +    }
    
    476
    +
    
    477
    +    @Test
    
    478
    +    fun `GIVEN a URL with a repeated domain WHEN rendering it THEN the last occurrence of domain is set`() {
    
    479
    +        runTestOnMain {
    
    480
    +            testRenderWithRegistrableDomain(
    
    481
    +                testUrl = "https://mozilla.org.mozilla.org/",
    
    482
    +                expectedUrl = "mozilla.org",
    
    483
    +            )
    
    484
    +        }
    
    485
    +    }
    
    486
    +
    
    487
    +    @Test
    
    488
    +    fun `GIVEN a URL with an IPv4 address WHEN rendering it THEN the IP part is set`() {
    
    489
    +        runTestOnMain {
    
    490
    +            testRenderWithRegistrableDomain(
    
    491
    +                testUrl = "http://127.0.0.1/",
    
    492
    +                expectedUrl = "127.0.0.1",
    
    493
    +            )
    
    494
    +        }
    
    495
    +    }
    
    496
    +
    
    497
    +    @Test
    
    498
    +    fun `GIVEN a URL with an IPv6 address WHEN rendering it THEN the IP part is set`() {
    
    499
    +        runTestOnMain {
    
    500
    +            testRenderWithRegistrableDomain(
    
    501
    +                testUrl = "http://[::1]/",
    
    502
    +                expectedUrl = "[::1]",
    
    503
    +            )
    
    504
    +        }
    
    505
    +    }
    
    506
    +
    
    507
    +    @Test
    
    508
    +    fun `GIVEN a URL with a non PSL domain WHEN rendering it THEN host set`() {
    
    509
    +        runTestOnMain {
    
    510
    +            testRenderWithRegistrableDomain(
    
    511
    +                testUrl = "http://localhost/",
    
    512
    +                expectedUrl = "localhost",
    
    513
    +            )
    
    514
    +        }
    
    515
    +    }
    
    516
    +
    
    517
    +    @Test
    
    518
    +    fun `GIVEN an internal page name WHEN rendering it THEN it is set`() {
    
    519
    +        runTestOnMain {
    
    520
    +            testRenderWithRegistrableDomain(
    
    521
    +                testUrl = "about:mozilla",
    
    522
    +                expectedUrl = "about:mozilla",
    
    523
    +            )
    
    524
    +        }
    
    525
    +    }
    
    526
    +
    
    527
    +    @Test
    
    528
    +    fun `GIVEN a content URI WHEN rendering it THEN it is set`() {
    
    529
    +        runTestOnMain {
    
    530
    +            testRenderWithRegistrableDomain(
    
    531
    +                testUrl = "content://media/external/file/1000000000",
    
    532
    +                expectedUrl = "content://media/external/file/1000000000",
    
    533
    +            )
    
    534
    +        }
    
    535
    +    }
    
    536
    +}
    
    537
    +
    
    538
    +/**
    
    539
    + * Robolectric default implementation of [InetAddresses] returns false for any address.
    
    540
    + * This shadow is used to override that behavior and return true for any IP address.
    
    541
    + */
    
    542
    +@Implements(InetAddresses::class)
    
    543
    +class ShadowInetAddresses {
    
    544
    +    companion object {
    
    545
    +        @Implementation
    
    546
    +        @JvmStatic
    
    547
    +        @Suppress("DEPRECATION")
    
    548
    +        fun isNumericAddress(address: String): Boolean {
    
    549
    +            return Patterns.IP_ADDRESS.matcher(address).matches() || address.contains(":")
    
    550
    +        }
    
    551
    +    }
    
    175 552
     }

  • mobile/android/android-components/components/support/utils/src/main/java/mozilla/components/support/ktx/util/URLStringUtils.kt
    ... ... @@ -7,8 +7,6 @@ package mozilla.components.support.ktx.util
    7 7
     import android.text.TextUtils
    
    8 8
     import androidx.annotation.VisibleForTesting
    
    9 9
     import androidx.core.net.toUri
    
    10
    -import androidx.core.text.TextDirectionHeuristicCompat
    
    11
    -import androidx.core.text.TextDirectionHeuristicsCompat
    
    12 10
     import java.util.regex.Pattern
    
    13 11
     
    
    14 12
     object URLStringUtils {
    
    ... ... @@ -102,25 +100,9 @@ object URLStringUtils {
    102 100
         /**
    
    103 101
          * Generates a shorter version of the provided URL for display purposes by stripping it of
    
    104 102
          * https/http and/or WWW prefixes and/or trailing slash when applicable.
    
    105
    -     *
    
    106
    -     * The returned text will always be displayed from left to right.
    
    107
    -     * If the directionality would otherwise be RTL "\u200E" will be prepended to the result to force LTR.
    
    108 103
          */
    
    109
    -    fun toDisplayUrl(
    
    110
    -        originalUrl: CharSequence,
    
    111
    -        textDirectionHeuristic: TextDirectionHeuristicCompat = TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR,
    
    112
    -    ): CharSequence {
    
    113
    -        val strippedText = maybeStripTrailingSlash(maybeStripUrlProtocol(originalUrl))
    
    114
    -
    
    115
    -        return if (
    
    116
    -            strippedText.isNotBlank() &&
    
    117
    -            textDirectionHeuristic.isRtl(strippedText, 0, 1)
    
    118
    -        ) {
    
    119
    -            "\u200E" + strippedText
    
    120
    -        } else {
    
    121
    -            strippedText
    
    122
    -        }
    
    123
    -    }
    
    104
    +    fun toDisplayUrl(originalUrl: CharSequence): CharSequence =
    
    105
    +        maybeStripTrailingSlash(maybeStripUrlProtocol(originalUrl))
    
    124 106
     
    
    125 107
         private fun maybeStripUrlProtocol(url: CharSequence): CharSequence {
    
    126 108
             if (url.startsWith(HTTPS)) {
    

  • mobile/android/android-components/components/support/utils/src/test/java/mozilla/components/support/utils/URLStringUtilsTest.kt
    ... ... @@ -16,8 +16,6 @@ import org.junit.Assert.assertTrue
    16 16
     import org.junit.Before
    
    17 17
     import org.junit.Test
    
    18 18
     import org.junit.runner.RunWith
    
    19
    -import org.mockito.Mockito.spy
    
    20
    -import org.mockito.Mockito.verify
    
    21 19
     import kotlin.random.Random
    
    22 20
     
    
    23 21
     @RunWith(AndroidJUnit4::class)
    
    ... ... @@ -246,20 +244,9 @@ class URLStringUtilsTest {
    246 244
         }
    
    247 245
     
    
    248 246
         @Test
    
    249
    -    fun showDisplayUrlAsLTREvenIfTextStartsWithArabicCharacters() {
    
    247
    +    fun toDisplayUrlDoesNotAddImplicitDirectionalMarks() {
    
    250 248
             val testDisplayUrl = URLStringUtils.toDisplayUrl("http://ختار.ار/www.mozilla.org/1")
    
    251
    -        assertEquals("\u200Eختار.ار/www.mozilla.org/1", testDisplayUrl)
    
    252
    -    }
    
    253
    -
    
    254
    -    @Test
    
    255
    -    fun toDisplayUrlAlwaysUseATextDirectionHeuristicToDetermineDirectionality() {
    
    256
    -        val textHeuristic = spy(TestTextDirectionHeuristicCompat())
    
    257
    -
    
    258
    -        URLStringUtils.toDisplayUrl("http://ختار.ار/www.mozilla.org/1", textHeuristic)
    
    259
    -        verify(textHeuristic).isRtl("ختار.ار/www.mozilla.org/1", 0, 1)
    
    260
    -
    
    261
    -        URLStringUtils.toDisplayUrl("http://www.mozilla.org/1", textHeuristic)
    
    262
    -        verify(textHeuristic).isRtl("mozilla.org/1", 0, 1)
    
    249
    +        assertEquals("ختار.ار/www.mozilla.org/1", testDisplayUrl)
    
    263 250
         }
    
    264 251
     
    
    265 252
         @Test
    

  • mobile/android/components/geckoview/GeckoViewStreamListener.cpp
    ... ... @@ -16,6 +16,8 @@
    16 16
     #include "nsIWebProgressListener.h"
    
    17 17
     #include "nsIX509Cert.h"
    
    18 18
     #include "nsPrintfCString.h"
    
    19
    +#include "nsContentSecurityUtils.h"
    
    20
    +#include "nsITransfer.h"
    
    19 21
     
    
    20 22
     #include "nsNetUtil.h"
    
    21 23
     
    
    ... ... @@ -85,6 +87,16 @@ GeckoViewStreamListener::OnStartRequest(nsIRequest* aRequest) {
    85 87
         return NS_OK;
    
    86 88
       }
    
    87 89
     
    
    90
    +  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
    
    91
    +  if (channel) {
    
    92
    +    int32_t classification = nsContentSecurityUtils::ClassifyDownload(channel);
    
    93
    +    if (classification == nsITransfer::DOWNLOAD_FORBIDDEN) {
    
    94
    +      channel->Cancel(NS_ERROR_ABORT);
    
    95
    +      CompleteWithError(NS_ERROR_ABORT, channel);
    
    96
    +      return NS_OK;
    
    97
    +    }
    
    98
    +  }
    
    99
    +
    
    88 100
       // We're expecting data later via OnDataAvailable, so create the stream now.
    
    89 101
       InitializeStreamSupport(aRequest);
    
    90 102
     
    

  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt
    ... ... @@ -58,6 +58,7 @@ abstract class ToolbarIntegration(
    58 58
             urlRenderConfiguration = ToolbarFeature.UrlRenderConfiguration(
    
    59 59
                 context.components.publicSuffixList,
    
    60 60
                 context.getColorFromAttr(R.attr.textPrimary),
    
    61
    +            context.getColorFromAttr(R.attr.textSecondary),
    
    61 62
                 renderStyle = renderStyle,
    
    62 63
             ),
    
    63 64
         )
    
    ... ... @@ -140,7 +141,7 @@ class DefaultToolbarIntegration(
    140 141
         interactor = interactor,
    
    141 142
         customTabId = customTabId,
    
    142 143
         isPrivate = isPrivate,
    
    143
    -    renderStyle = ToolbarFeature.RenderStyle.UncoloredUrl,
    
    144
    +    renderStyle = ToolbarFeature.RenderStyle.ColoredUrl,
    
    144 145
     ) {
    
    145 146
     
    
    146 147
         @VisibleForTesting
    

  • testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_block_downloads.tentative.html.ini
    ... ... @@ -6,7 +6,6 @@
    6 6
           if (os == "linux") and not fission and not debug: [PASS, FAIL]
    
    7 7
           if (os == "mac") and debug: [PASS, FAIL]
    
    8 8
           if (os == "mac") and not debug: [PASS, FAIL]
    
    9
    -      if os == "android": FAIL
    
    10 9
     
    
    11 10
       [<a download> triggered download in sandbox is blocked before a request is made.]
    
    12 11
         expected: FAIL
    
    ... ... @@ -15,15 +14,12 @@
    15 14
         expected:
    
    16 15
           if (os == "mac") and debug: [PASS, FAIL]
    
    17 16
           if (os == "mac") and not debug: [PASS, FAIL]
    
    18
    -      if os == "android": FAIL
    
    19 17
     
    
    20 18
       [<a target="_blank" > triggered download in sandbox is blocked.]
    
    21 19
         expected:
    
    22 20
           if (os == "mac") and debug: [PASS, FAIL]
    
    23 21
           if (os == "mac") and not debug: [PASS, FAIL]
    
    24
    -      if os == "android": FAIL
    
    25 22
     
    
    26 23
       [<a target="_blank" rel="noopener" > triggered download in sandbox is blocked.]
    
    27 24
         expected:
    
    28 25
           if (os == "mac") and debug: [PASS, FAIL]
    29
    -      if os == "android": FAIL

  • testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_block_downloads.sub.tentative.html.ini
    ... ... @@ -2,10 +2,8 @@
    2 2
       [Navigation resulted download in sandbox is blocked.]
    
    3 3
         expected:
    
    4 4
           if (os == "mac") and not debug: [PASS, FAIL]
    
    5
    -      if os == "android": FAIL
    
    6 5
     
    
    7 6
       [Navigation resulted download in sandbox from <object> is blocked.]
    
    8 7
         expected:
    
    9 8
           if (os == "mac") and debug: [PASS, FAIL]
    
    10 9
           if (os == "mac") and not debug: [PASS, FAIL]
    11
    -      if os == "android": FAIL

  • testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_window_open_download_block_downloads.tentative.html.ini
    ... ... @@ -3,17 +3,15 @@
    3 3
         expected:
    
    4 4
           if (os == "linux") and debug and not fission: [PASS, FAIL]
    
    5 5
           if (os == "linux") and not debug: [PASS, FAIL]
    
    6
    -      if os == "android": FAIL
    
    7 6
     
    
    8 7
       [window.open(download, "_blank") triggering download in sandbox is blocked.]
    
    9 8
         expected:
    
    10 9
           if (os == "mac") and debug: [PASS, FAIL]
    
    11 10
           if (os == "linux") and not debug: [PASS, FAIL]
    
    12
    -      if os == "android": FAIL
    
    13 11
     
    
    14 12
       [window.open(download, "_blank", "noopener") triggering download in sandbox is blocked.]
    
    15 13
         expected:
    
    16 14
           if (os == "linux") and debug: PASS
    
    17 15
           if os == "win": PASS
    
    18
    -      if os == "android": FAIL
    
    16
    +      if os == "android": PASS
    
    19 17
           [PASS, FAIL]

  • testing/web-platform/tests/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_download_helper.js
    1 1
     function StreamDownloadFinishDelay() {
    
    2
    -    return 1000;
    
    2
    +    return 2000;
    
    3 3
     }
    
    4 4
     
    
    5 5
     function DownloadVerifyDelay() {
    

  • uriloader/exthandler/nsExternalHelperAppService.cpp
    ... ... @@ -1626,8 +1626,7 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
    1626 1626
         return NS_OK;
    
    1627 1627
       }
    
    1628 1628
     
    
    1629
    -  mDownloadClassification =
    
    1630
    -      nsContentSecurityUtils::ClassifyDownload(aChannel, MIMEType);
    
    1629
    +  mDownloadClassification = nsContentSecurityUtils::ClassifyDownload(aChannel);
    
    1631 1630
     
    
    1632 1631
       if (mDownloadClassification == nsITransfer::DOWNLOAD_FORBIDDEN) {
    
    1633 1632
         // If the download is rated as forbidden,