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

[tor-commits] [Git][tpo/applications/tor-browser][tor-browser-153.4.0esr-16.0-1] 2 commits: Bug 2054384 - Download notifications should truncate long filenames in the...



Title: GitLab

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

Commits:

  • 5bdc9fc9
    by giorga at 2026-09-26T19:19:21+02:00
    Bug 2054384 - Download notifications should truncate long filenames in the middle. r=android-reviewers,tthibaud
    
    Differential Revision: https://phabricator.services.mozilla.com/D321546
    
  • 2b4271f7
    by Rob Wu at 2026-09-27T17:17:54+02:00
    Bug 2061470 - Don't drop __proto__ from webRequest bodies r=rpl
    
    Differential Revision: https://phabricator.services.mozilla.com/D323631
    

6 changed files:

Changes:

  • mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadNotification.kt
    ... ... @@ -28,6 +28,7 @@ import mozilla.components.feature.downloads.AbstractFetchDownloadService.Compani
    28 28
     import mozilla.components.feature.downloads.AbstractFetchDownloadService.Companion.ACTION_RESUME
    
    29 29
     import mozilla.components.feature.downloads.AbstractFetchDownloadService.Companion.ACTION_TRY_AGAIN
    
    30 30
     import mozilla.components.feature.downloads.AbstractFetchDownloadService.DownloadJobState
    
    31
    +import mozilla.components.support.ktx.kotlin.truncateMiddle
    
    31 32
     import mozilla.components.support.utils.DownloadFileUtils
    
    32 33
     import kotlin.random.Random
    
    33 34
     import kotlin.time.Duration.Companion.seconds
    
    ... ... @@ -40,6 +41,7 @@ internal object DownloadNotification {
    40 41
         internal const val NOTIFICATION_DOWNLOAD_GROUP_ID = 100
    
    41 42
         private const val LEGACY_NOTIFICATION_CHANNEL_ID = "Downloads"
    
    42 43
         internal const val PERCENTAGE_MULTIPLIER = 100
    
    44
    +    private const val MAX_FILENAME_LENGTH = 25
    
    43 45
     
    
    44 46
         @VisibleForTesting
    
    45 47
         internal fun createDownloadGroupNotification(
    
    ... ... @@ -89,11 +91,12 @@ internal object DownloadNotification {
    89 91
             val channelId = ensureChannelExists(context)
    
    90 92
             val isIndeterminate = downloadState.isIndeterminate()
    
    91 93
             val percentCopied = downloadState.getPercent() ?: -1
    
    94
    +        val fileName = downloadState.fileName?.truncateMiddle(MAX_FILENAME_LENGTH).orEmpty()
    
    92 95
     
    
    93 96
             return NotificationCompat.Builder(context, channelId)
    
    94 97
                 .setStyle(
    
    95 98
                     NotificationCompat.BigTextStyle()
    
    96
    -                    .setBigContentTitle(downloadState.fileName.orEmpty())
    
    99
    +                    .setBigContentTitle(fileName)
    
    97 100
                         .setSummaryText(
    
    98 101
                             formatDownloadTimeRemaining(
    
    99 102
                                 context = context,
    
    ... ... @@ -105,10 +108,8 @@ internal object DownloadNotification {
    105 108
                         ),
    
    106 109
                 )
    
    107 110
                 .setSmallIcon(R.drawable.mozac_feature_download_ic_ongoing_download)
    
    108
    -            .setContentTitle(downloadState.fileName.orEmpty())
    
    109
    -            .setContentText(
    
    110
    -                downloadState.getProgress(fileSizeFormatter = fileSizeFormatter),
    
    111
    -            )
    
    111
    +            .setContentTitle(fileName)
    
    112
    +            .setContentText(downloadState.getProgress(fileSizeFormatter = fileSizeFormatter))
    
    112 113
                 .setColor(ContextCompat.getColor(context, notificationAccentColor))
    
    113 114
                 .setCategory(NotificationCompat.CATEGORY_PROGRESS)
    
    114 115
                 .setProgress(PERCENTAGE_MULTIPLIER, percentCopied, isIndeterminate)
    
    ... ... @@ -132,10 +133,11 @@ internal object DownloadNotification {
    132 133
             notificationAccentColor: Int,
    
    133 134
         ): Notification {
    
    134 135
             val channelId = ensureChannelExists(context)
    
    136
    +        val fileName = downloadState.fileName?.truncateMiddle(MAX_FILENAME_LENGTH).orEmpty()
    
    135 137
     
    
    136 138
             return NotificationCompat.Builder(context, channelId)
    
    137 139
                 .setSmallIcon(R.drawable.mozac_feature_download_ic_download)
    
    138
    -            .setContentTitle(downloadState.fileName)
    
    140
    +            .setContentTitle(fileName)
    
    139 141
                 .setContentText(
    
    140 142
                     context.applicationContext.getString(R.string.mozac_feature_downloads_paused_notification_text),
    
    141 143
                 )
    
    ... ... @@ -163,10 +165,11 @@ internal object DownloadNotification {
    163 165
             contentIntent: PendingIntent = createOpenFilePendingIntent(context, downloadState, downloadFileUtils),
    
    164 166
         ): Notification {
    
    165 167
             val channelId = ensureChannelExists(context)
    
    168
    +        val fileName = downloadState.fileName?.truncateMiddle(MAX_FILENAME_LENGTH).orEmpty()
    
    166 169
     
    
    167 170
             return NotificationCompat.Builder(context, channelId)
    
    168 171
                 .setSmallIcon(R.drawable.mozac_feature_download_ic_download_complete)
    
    169
    -            .setContentTitle(downloadState.fileName)
    
    172
    +            .setContentTitle(fileName)
    
    170 173
                 .setWhen(createdTime)
    
    171 174
                 .setOnlyAlertOnce(true)
    
    172 175
                 .setContentText(
    
    ... ... @@ -191,10 +194,11 @@ internal object DownloadNotification {
    191 194
             notificationAccentColor: Int,
    
    192 195
         ): Notification {
    
    193 196
             val channelId = ensureChannelExists(context)
    
    197
    +        val fileName = downloadState.fileName?.truncateMiddle(MAX_FILENAME_LENGTH).orEmpty()
    
    194 198
     
    
    195 199
             return NotificationCompat.Builder(context, channelId)
    
    196 200
                 .setSmallIcon(R.drawable.mozac_feature_download_ic_download_failed)
    
    197
    -            .setContentTitle(downloadState.fileName)
    
    201
    +            .setContentTitle(fileName)
    
    198 202
                 .setContentText(
    
    199 203
                     context.applicationContext.getString(R.string.mozac_feature_downloads_failed_notification_text2),
    
    200 204
                 )
    
    ... ... @@ -217,7 +221,8 @@ internal object DownloadNotification {
    217 221
             notifications: List<DownloadJobState>,
    
    218 222
         ): List<String> {
    
    219 223
             return notifications.take(2).map { downloadState ->
    
    220
    -            "${downloadState.state.fileName} ${downloadState.state.getStatusDescription(
    
    224
    +            val fileName = downloadState.state.fileName.orEmpty().truncateMiddle(MAX_FILENAME_LENGTH)
    
    225
    +            "$fileName ${downloadState.state.getStatusDescription(
    
    221 226
                     context = context,
    
    222 227
                     fileSizeFormatter = fileSizeFormatter,
    
    223 228
                 )}"
    

  • mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadNotificationTest.kt
    ... ... @@ -4,6 +4,7 @@
    4 4
     
    
    5 5
     package mozilla.components.feature.downloads
    
    6 6
     
    
    7
    +import android.app.Notification
    
    7 8
     import android.app.PendingIntent
    
    8 9
     import androidx.core.app.NotificationCompat
    
    9 10
     import androidx.core.app.NotificationCompat.EXTRA_PROGRESS
    
    ... ... @@ -307,6 +308,32 @@ class DownloadNotificationTest {
    307 308
             assertEquals(true, notificationDownloadWithNoSize.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE))
    
    308 309
         }
    
    309 310
     
    
    311
    +    @Test
    
    312
    +    fun `createOngoingDownloadNotification with long filename truncates from middle`() {
    
    313
    +        val longFileName = "this_is_a_very_long_filename_that_exceeds_forty_characters_to_test_truncation.pdf"
    
    314
    +        val downloadState =
    
    315
    +            DownloadState(
    
    316
    +                fileName = longFileName,
    
    317
    +                url = "mozilla.org/file.pdf",
    
    318
    +                contentLength = 100L,
    
    319
    +                currentBytesCopied = 10,
    
    320
    +                status = DownloadState.Status.DOWNLOADING,
    
    321
    +            )
    
    322
    +        val style = AbstractFetchDownloadService.Style()
    
    323
    +
    
    324
    +        val notification =
    
    325
    +            DownloadNotification.createOngoingDownloadNotification(
    
    326
    +                context = testContext,
    
    327
    +                downloadState = downloadState,
    
    328
    +                fileSizeFormatter = fakeFileSizeFormatter,
    
    329
    +                notificationAccentColor = style.notificationAccentColor,
    
    330
    +                downloadEstimator = DownloadEstimator(dateTimeProvider = FakeDateTimeProvider()),
    
    331
    +            )
    
    332
    +
    
    333
    +        val expectedTitle = "this_is_a_ve…uncation.pdf"
    
    334
    +        assertEquals(expectedTitle, notification.extras.getCharSequence(Notification.EXTRA_TITLE).toString())
    
    335
    +    }
    
    336
    +
    
    310 337
         @Test
    
    311 338
         fun getOngoingNotificationAccentColor() {
    
    312 339
             val download = DownloadJobState(
    

  • mobile/android/android-components/components/support/ktx/src/main/java/mozilla/components/support/ktx/kotlin/String.kt
    ... ... @@ -507,3 +507,24 @@ internal fun String.extractBase6RawString(): String? {
    507 507
             contentString
    
    508 508
         }
    
    509 509
     }
    
    510
    +
    
    511
    +/**
    
    512
    + * Truncates the string from the middle if it exceeds [maxLength].
    
    513
    + *
    
    514
    + * @param maxLength The maximum length of the string.
    
    515
    + * @param ellipsis The string to use as ellipsis, defaults to "…".
    
    516
    + * @return The truncated string.
    
    517
    + */
    
    518
    +fun String.truncateMiddle(maxLength: Int, ellipsis: String = "…"): String {
    
    519
    +    if (length <= maxLength) {
    
    520
    +        return this
    
    521
    +    }
    
    522
    +    if (maxLength <= ellipsis.length) {
    
    523
    +        return take(maxLength)
    
    524
    +    }
    
    525
    +
    
    526
    +    val half = (maxLength - ellipsis.length) / 2
    
    527
    +    val start = take(half)
    
    528
    +    val end = takeLast(maxLength - ellipsis.length - half)
    
    529
    +    return "$start$ellipsis$end"
    
    530
    +}

  • mobile/android/android-components/components/support/ktx/src/test/java/mozilla/components/support/ktx/kotlin/StringTest.kt
    ... ... @@ -306,6 +306,16 @@ class StringTest {
    306 306
             }
    
    307 307
         }
    
    308 308
     
    
    309
    +    @Test
    
    310
    +    fun truncateMiddle() {
    
    311
    +        assertEquals("123…890", "1234567890".truncateMiddle(7))
    
    312
    +        assertEquals("1234567890", "1234567890".truncateMiddle(10))
    
    313
    +        assertEquals("1234567890", "1234567890".truncateMiddle(20))
    
    314
    +        assertEquals("1…0", "1234567890".truncateMiddle(3))
    
    315
    +        assertEquals("1", "1234567890".truncateMiddle(1))
    
    316
    +        assertEquals("123...890", "1234567890".truncateMiddle(9, "..."))
    
    317
    +    }
    
    318
    +
    
    309 319
         @Test
    
    310 320
         fun `WHEN a string contains utf 8 encoded characters or illegal filename characters decode decodes it`() {
    
    311 321
             // List of pairs of encoded strings or illegal filename characters and their expected decoded results
    

  • toolkit/components/extensions/test/mochitest/test_ext_webrequest_upload.html
    ... ... @@ -27,6 +27,7 @@
    27 27
       enctype="multipart/form-data"
    
    28 28
       >
    
    29 29
     <input type="text" name="textInput2" value="value2">
    
    30
    +<input type="text" name="__proto__" value="regression test for bug 2061470">
    
    30 31
     <input type="file" name="testFile">
    
    31 32
     <input type="file" name="emptyFile">
    
    32 33
     </form>
    
    ... ... @@ -161,11 +162,19 @@ add_task(async function test_xhr_forms() {
    161 162
         }
    
    162 163
         let action = new URL(form.action);
    
    163 164
         let formData = new FormData(form);
    
    164
    -    let webRequestFD = {};
    
    165 165
     
    
    166 166
         let updateActionURL = () => {
    
    167
    +      let webRequestFD = {};
    
    167 168
           for (let name of formData.keys()) {
    
    168
    -        webRequestFD[name] = name in uploads ? [uploads[name].fileName] : formData.getAll(name);
    
    169
    +        if (Object.hasOwn(webRequestFD, name)) {
    
    170
    +          // Ignore duplicate keys; formData.getAll already read all values.
    
    171
    +          continue;
    
    172
    +        }
    
    173
    +        const value = Object.hasOwn(uploads, name) ? [uploads[name].fileName] : formData.getAll(name);
    
    174
    +        // Cannot use webRequestFD[name] = value, because for "__proto__" as
    
    175
    +        // name, that would trigger the Object.prototype.__proto__ setter
    
    176
    +        // instead of defining a data property.
    
    177
    +        Object.defineProperty(webRequestFD, name, { value, enumerable: true });
    
    169 178
           }
    
    170 179
           action.searchParams.set("upload", JSON.stringify(webRequestFD));
    
    171 180
           action.searchParams.set("enctype", form.enctype);
    

  • toolkit/components/extensions/webrequest/WebRequestUpload.sys.mjs
    ... ... @@ -117,22 +117,6 @@ class Headers extends Map {
    117 117
       }
    
    118 118
     }
    
    119 119
     
    
    120
    -/**
    
    121
    - * Creates a new Object with a corresponding property for every
    
    122
    - * key-value pair in the given Map.
    
    123
    - *
    
    124
    - * @param {Map} map
    
    125
    - *        The map to convert.
    
    126
    - * @returns {object}
    
    127
    - */
    
    128
    -function mapToObject(map) {
    
    129
    -  let result = {};
    
    130
    -  for (let [key, value] of map) {
    
    131
    -    result[key] = value;
    
    132
    -  }
    
    133
    -  return result;
    
    134
    -}
    
    135
    -
    
    136 120
     /**
    
    137 121
      * Rewinds the given seekable input stream to its beginning, and catches
    
    138 122
      * any resulting errors.
    
    ... ... @@ -446,7 +430,7 @@ function createFormData(stream, channel, lenient) {
    446 430
       try {
    
    447 431
         let formData = parseFormData(stream, channel, lenient);
    
    448 432
         if (formData) {
    
    449
    -      return mapToObject(formData);
    
    433
    +      return Object.fromEntries(formData);
    
    450 434
         }
    
    451 435
       } catch (e) {
    
    452 436
         Cu.reportError(e);
    

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