[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [Git][tpo/applications/tor-browser][tor-browser-140.1.0esr-15.0-1] 11 commits: Bug 1961829 - Only color HTTP(S) URIs and fallback to coloring the host in...



Title: GitLab

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

Commits:

  • 421e0a1f
    by Michel Le Bihan at 2025-07-24T08:15:34+02:00
    Bug 1961829 - Only color HTTP(S) URIs and fallback to coloring the host in Android toolbar URLRenderer. r=tthibaud,android-reviewers
    
    Differential Revision: https://phabricator.services.mozilla.com/D248132
    
  • 116d90bb
    by Michel Le Bihan at 2025-07-24T08:15:36+02:00
    Bug 1961757 - Set text direction in Android toolbar instead of adding directional marks. r=tthibaud,android-reviewers,petru
    
    Differential Revision: https://phabricator.services.mozilla.com/D246181
    
  • 52e6b98a
    by Michel Le Bihan at 2025-07-24T08:15:37+02:00
    Bug 1812898 - Part 1: Add domain alignment in Android toolbar component. r=tthibaud,android-reviewers
    
    Differential Revision: https://phabricator.services.mozilla.com/D244508
    
  • 84b3a932
    by Michel Le Bihan at 2025-07-24T08:15:39+02:00
    Bug 1812898 - Part 2: Enable domain highlighting in Fenix toolbar. r=tthibaud,android-reviewers
    
    Differential Revision: https://phabricator.services.mozilla.com/D244509
    
  • f0723224
    by mimi89999 at 2025-07-24T08:15:41+02:00
    Bug 1964251 - Replace logic of RegistrableDomain renderStyle in Android toolbar component. r=android-reviewers,petru
    
    Differential Revision: https://phabricator.services.mozilla.com/D251501
    
  • 52cd8fb2
    by Michel Le Bihan at 2025-07-24T08:15:42+02:00
    Bug 1969937 - Add handling of blob URIs in Android toolbar URLRenderer. r=petru,android-reviewers
    
    Differential Revision: https://phabricator.services.mozilla.com/D252879
    
  • aeb2a384
    by Cathy Lu at 2025-07-24T08:15:44+02:00
    Bug 1791322 - iframe sandbox wpt tests modified with delay r=nika
    
    Differential Revision: https://phabricator.services.mozilla.com/D253052
    
  • b2e55448
    by Cathy Lu at 2025-07-24T08:15:45+02:00
    Bug 1791322 - GeckoView should call classifyDownloads to sandbox downloads r=geckoview-reviewers,nika
    
    Differential Revision: https://phabricator.services.mozilla.com/D249683
    
  • a6c81f5b
    by Andreas Pehrson at 2025-07-24T08:15:47+02:00
    Bug 1971116 - For global mute events, iterate on copies of containers. r=dbaker
    
    Mute/unmute events are fired synchronously to content, which if it stops an
    (event target) track in the event handler, may call back into and mutate the
    containers we're iterating over.
    
    Differential Revision: https://phabricator.services.mozilla.com/D254352
    
  • 1684fbd9
    by Tom Schuster at 2025-07-24T08:15:48+02:00
    Bug 1971704 - Cleanup nsContentSecurityUtils::ClassifyDownload. r=smaug
    
    Differential Revision: https://phabricator.services.mozilla.com/D253491
    
  • 22375264
    by Pier Angelo Vendrame at 2025-07-24T08:15:50+02:00
    Bug 1972282 - Check for spoof English in xsl:sort. r=smaug
    
    Differential Revision: https://phabricator.services.mozilla.com/D254784
    

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

  • 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
    
    ... ... @@ -549,6 +551,13 @@ interface Toolbar : ScrollableToolbar {
    549 551
              */
    
    550 552
             END,
    
    551 553
         }
    
    554
    +
    
    555
    +    /**
    
    556
    +     * Registrable domain foreground color span.
    
    557
    +     *
    
    558
    +     * This simple class extension is used so that we can filter for it elsewhere.
    
    559
    +     */
    
    560
    +    class RegistrableDomainColorSpan(@ColorInt color: Int) : ForegroundColorSpan(color)
    
    552 561
     }
    
    553 562
     
    
    554 563
     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,
    

  • _______________________________________________
    tor-commits mailing list -- tor-commits@xxxxxxxxxxxxxxxxxxxx
    To unsubscribe send an email to tor-commits-leave@xxxxxxxxxxxxxxxxxxxx