richard pushed to branch tor-browser-115.10.0esr-13.5-1 at The Tor Project / Applications / Tor Browser

Commits:

6 changed files:

Changes:

  • browser/components/torpreferences/content/connectionPane.xhtml
    ... ... @@ -48,7 +48,7 @@
    48 48
               class="network-status-label"
    
    49 49
               data-l10n-id="tor-connection-internet-status-label"
    
    50 50
             ></html:span>
    
    51
    -        <img alt="" class="network-status-loading-icon" />
    
    51
    +        <img alt="" class="network-status-loading-icon tor-loading-icon" />
    
    52 52
             <html:span class="network-status-result"></html:span>
    
    53 53
           </html:div>
    
    54 54
           <html:button
    

  • browser/components/torpreferences/content/loxInviteDialog.js
    ... ... @@ -62,12 +62,12 @@ const gLoxInvites = {
    62 62
         this._remainingInvitesEl = document.getElementById(
    
    63 63
           "lox-invite-dialog-remaining"
    
    64 64
         );
    
    65
    +    this._generateArea = document.getElementById(
    
    66
    +      "lox-invite-dialog-generate-area"
    
    67
    +    );
    
    65 68
         this._generateButton = document.getElementById(
    
    66 69
           "lox-invite-dialog-generate-button"
    
    67 70
         );
    
    68
    -    this._connectingEl = document.getElementById(
    
    69
    -      "lox-invite-dialog-connecting"
    
    70
    -    );
    
    71 71
         this._errorEl = document.getElementById("lox-invite-dialog-error-message");
    
    72 72
         this._inviteListEl = document.getElementById("lox-invite-dialog-list");
    
    73 73
     
    
    ... ... @@ -237,20 +237,46 @@ const gLoxInvites = {
    237 237
       _setGenerating(isGenerating) {
    
    238 238
         this._generating = isGenerating;
    
    239 239
         this._updateGenerateButtonState();
    
    240
    -    this._connectingEl.classList.toggle("show-connecting", isGenerating);
    
    240
    +    this._generateArea.classList.toggle("show-connecting", isGenerating);
    
    241 241
       },
    
    242 242
     
    
    243
    +  /**
    
    244
    +   * Whether the generate button is disabled.
    
    245
    +   *
    
    246
    +   * @type {boolean}
    
    247
    +   */
    
    248
    +  _generateDisabled: false,
    
    243 249
       /**
    
    244 250
        * Update the state of the generate button.
    
    245 251
        */
    
    246 252
       _updateGenerateButtonState() {
    
    247
    -    this._generateButton.disabled = this._generating || !this._remainingInvites;
    
    253
    +    const disabled = this._generating || !this._remainingInvites;
    
    254
    +    this._generateDisabled = disabled;
    
    255
    +    // When generating we use "aria-disabled" rather than the "disabled"
    
    256
    +    // attribute so that the button can remain focusable whilst we generate
    
    257
    +    // invites.
    
    258
    +    // NOTE: When we generate the invite the focus will move to the invite list,
    
    259
    +    // so it should be safe to make the button non-focusable in this case.
    
    260
    +    const spoofDisabled = this._generating;
    
    261
    +    this._generateButton.disabled = disabled && !spoofDisabled;
    
    262
    +    this._generateButton.classList.toggle(
    
    263
    +      "spoof-button-disabled",
    
    264
    +      spoofDisabled
    
    265
    +    );
    
    266
    +    if (spoofDisabled) {
    
    267
    +      this._generateButton.setAttribute("aria-disabled", "true");
    
    268
    +    } else {
    
    269
    +      this._generateButton.removeAttribute("aria-disabled");
    
    270
    +    }
    
    248 271
       },
    
    249 272
     
    
    250 273
       /**
    
    251 274
        * Start generating a new invite.
    
    252 275
        */
    
    253 276
       _generateNewInvite() {
    
    277
    +    if (this._generateDisabled) {
    
    278
    +      return;
    
    279
    +    }
    
    254 280
         if (this._generating) {
    
    255 281
           console.error("Already generating an invite");
    
    256 282
           return;
    
    ... ... @@ -258,15 +284,13 @@ const gLoxInvites = {
    258 284
         this._setGenerating(true);
    
    259 285
         // Clear the previous error.
    
    260 286
         this._updateGenerateError(null);
    
    261
    -    // Move focus from the button to the connecting element, since button is
    
    262
    -    // now disabled.
    
    263
    -    this._connectingEl.focus();
    
    264 287
     
    
    265
    -    let lostFocus = false;
    
    288
    +    let moveFocus = false;
    
    266 289
         Lox.generateInvite(this._loxId)
    
    267 290
           .finally(() => {
    
    268
    -        // Fetch whether the connecting label still has focus before we hide it.
    
    269
    -        lostFocus = this._connectingEl.contains(document.activeElement);
    
    291
    +        // Fetch whether the generate button has focus before we potentially
    
    292
    +        // disable it.
    
    293
    +        moveFocus = this._generateButton.contains(document.activeElement);
    
    270 294
             this._setGenerating(false);
    
    271 295
           })
    
    272 296
           .then(
    
    ... ... @@ -279,7 +303,7 @@ const gLoxInvites = {
    279 303
                 this._inviteListEl.selectedIndex = 0;
    
    280 304
               }
    
    281 305
     
    
    282
    -          if (lostFocus) {
    
    306
    +          if (moveFocus) {
    
    283 307
                 // Move focus to the new invite before we hide the "Connecting"
    
    284 308
                 // message.
    
    285 309
                 this._inviteListEl.focus();
    
    ... ... @@ -295,12 +319,6 @@ const gLoxInvites = {
    295 319
                   this._updateGenerateError("generic");
    
    296 320
                   break;
    
    297 321
               }
    
    298
    -
    
    299
    -          if (lostFocus) {
    
    300
    -            // Move focus back to the button before we hide the "Connecting"
    
    301
    -            // message.
    
    302
    -            this._generateButton.focus();
    
    303
    -          }
    
    304 322
             }
    
    305 323
           );
    
    306 324
       },
    
    ... ... @@ -315,7 +333,7 @@ const gLoxInvites = {
    315 333
         // First clear the existing error.
    
    316 334
         this._errorEl.removeAttribute("data-l10n-id");
    
    317 335
         this._errorEl.textContent = "";
    
    318
    -    this._errorEl.classList.toggle("show-error", !!type);
    
    336
    +    this._generateArea.classList.toggle("show-error", !!type);
    
    319 337
     
    
    320 338
         if (!type) {
    
    321 339
           return;
    

  • browser/components/torpreferences/content/loxInviteDialog.xhtml
    ... ... @@ -40,10 +40,14 @@
    40 40
               id="lox-invite-dialog-error-message"
    
    41 41
               role="alert"
    
    42 42
             ></html:span>
    
    43
    +        <img
    
    44
    +          id="lox-invite-dialog-loading-icon"
    
    45
    +          class="tor-loading-icon"
    
    46
    +          alt=""
    
    47
    +        />
    
    43 48
             <html:span
    
    44 49
               id="lox-invite-dialog-connecting"
    
    45 50
               role="alert"
    
    46
    -          tabindex="0"
    
    47 51
               data-l10n-id="lox-invite-dialog-connecting"
    
    48 52
             ></html:span>
    
    49 53
           </html:div>
    

  • browser/components/torpreferences/content/provideBridgeDialog.js
    ... ... @@ -84,13 +84,19 @@ const gProvideBridgeDialog = {
    84 84
     
    
    85 85
         this._dialog = document.getElementById("user-provide-bridge-dialog");
    
    86 86
         this._acceptButton = this._dialog.getButton("accept");
    
    87
    +
    
    88
    +    // Inject our stylesheet into the shadow root so that the accept button can
    
    89
    +    // take the spoof-button-disabled styling.
    
    90
    +    const styleLink = document.createElement("link");
    
    91
    +    styleLink.rel = "stylesheet";
    
    92
    +    styleLink.href =
    
    93
    +      "chrome://browser/content/torpreferences/torPreferences.css";
    
    94
    +    this._dialog.shadowRoot.append(styleLink);
    
    95
    +
    
    87 96
         this._textarea = document.getElementById("user-provide-bridge-textarea");
    
    88 97
         this._errorEl = document.getElementById(
    
    89 98
           "user-provide-bridge-error-message"
    
    90 99
         );
    
    91
    -    this._connectingEl = document.getElementById(
    
    92
    -      "user-provide-bridge-connecting"
    
    93
    -    );
    
    94 100
         this._resultDescription = document.getElementById(
    
    95 101
           "user-provide-result-description"
    
    96 102
         );
    
    ... ... @@ -152,13 +158,16 @@ const gProvideBridgeDialog = {
    152 158
        * Reset focus position in the dialog.
    
    153 159
        */
    
    154 160
       takeFocus() {
    
    155
    -    if (this._page === "entry") {
    
    156
    -      this._textarea.focus();
    
    157
    -    } else {
    
    158
    -      // Move focus to the <xul:window> element.
    
    159
    -      // In particular, we do not want to keep the focus on the (same) accept
    
    160
    -      // button (with now different text).
    
    161
    -      document.documentElement.focus();
    
    161
    +    switch (this._page) {
    
    162
    +      case "entry":
    
    163
    +        this._textarea.focus();
    
    164
    +        break;
    
    165
    +      case "result":
    
    166
    +        // Move focus to the table.
    
    167
    +        // In particular, we do not want to keep the focus on the (same) accept
    
    168
    +        // button (with now different text).
    
    169
    +        this._bridgeGrid.focus();
    
    170
    +        break;
    
    162 171
         }
    
    163 172
       },
    
    164 173
     
    
    ... ... @@ -193,12 +202,27 @@ const gProvideBridgeDialog = {
    193 202
         }
    
    194 203
       },
    
    195 204
     
    
    205
    +  /**
    
    206
    +   * Whether the dialog accept button is disabled.
    
    207
    +   *
    
    208
    +   * @type {boolean}
    
    209
    +   */
    
    210
    +  _acceptDisabled: false,
    
    196 211
       /**
    
    197 212
        * Callback for whenever the accept button's might need to be disabled.
    
    198 213
        */
    
    199 214
       updateAcceptDisabled() {
    
    200
    -    this._acceptButton.disabled =
    
    215
    +    const disabled =
    
    201 216
           this._page === "entry" && (this.isEmpty() || this._loxLoading);
    
    217
    +    this._acceptDisabled = disabled;
    
    218
    +    // Spoof the button to look and act as if it is disabled, but still allow
    
    219
    +    // keyboard focus so the user can sit on this button whilst we are loading.
    
    220
    +    this._acceptButton.classList.toggle("spoof-button-disabled", disabled);
    
    221
    +    if (disabled) {
    
    222
    +      this._acceptButton.setAttribute("aria-disabled", "true");
    
    223
    +    } else {
    
    224
    +      this._acceptButton.removeAttribute("aria-disabled");
    
    225
    +    }
    
    202 226
       },
    
    203 227
     
    
    204 228
       /**
    
    ... ... @@ -217,16 +241,7 @@ const gProvideBridgeDialog = {
    217 241
       setLoxLoading(isLoading) {
    
    218 242
         this._loxLoading = isLoading;
    
    219 243
         this._textarea.readOnly = isLoading;
    
    220
    -    this._connectingEl.classList.toggle("show-connecting", isLoading);
    
    221
    -    if (
    
    222
    -      isLoading &&
    
    223
    -      this._acceptButton.contains(
    
    224
    -        this._acceptButton.getRootNode().activeElement
    
    225
    -      )
    
    226
    -    ) {
    
    227
    -      // Move focus to the alert before we disable the button.
    
    228
    -      this._connectingEl.focus();
    
    229
    -    }
    
    244
    +    this._dialog.classList.toggle("show-connecting", isLoading);
    
    230 245
         this.updateAcceptDisabled();
    
    231 246
       },
    
    232 247
     
    
    ... ... @@ -236,6 +251,12 @@ const gProvideBridgeDialog = {
    236 251
        * @param {Event} event - The dialogaccept event.
    
    237 252
        */
    
    238 253
       onDialogAccept(event) {
    
    254
    +    if (this._acceptDisabled) {
    
    255
    +      // Prevent closing.
    
    256
    +      event.preventDefault();
    
    257
    +      return;
    
    258
    +    }
    
    259
    +
    
    239 260
         if (this._page === "result") {
    
    240 261
           this._result.accepted = true;
    
    241 262
           // Continue to close the dialog.
    
    ... ... @@ -313,14 +334,11 @@ const gProvideBridgeDialog = {
    313 334
         this._errorEl.textContent = "";
    
    314 335
         if (error) {
    
    315 336
           this._textarea.setAttribute("aria-invalid", "true");
    
    316
    -      // Move focus back to the text area, likely away from the Next button or
    
    317
    -      // the "Connecting..." alert.
    
    318
    -      this._textarea.focus();
    
    319 337
         } else {
    
    320 338
           this._textarea.removeAttribute("aria-invalid");
    
    321 339
         }
    
    322 340
         this._textarea.classList.toggle("invalid-input", !!error);
    
    323
    -    this._errorEl.classList.toggle("show-error", !!error);
    
    341
    +    this._dialog.classList.toggle("show-error", !!error);
    
    324 342
     
    
    325 343
         if (!error) {
    
    326 344
           return;
    

  • browser/components/torpreferences/content/provideBridgeDialog.xhtml
    ... ... @@ -51,10 +51,14 @@
    51 51
               role="alert"
    
    52 52
               aria-live="assertive"
    
    53 53
             ></html:span>
    
    54
    +        <img
    
    55
    +          id="user-provide-bridge-loading-icon"
    
    56
    +          class="tor-loading-icon"
    
    57
    +          alt=""
    
    58
    +        />
    
    54 59
             <html:span
    
    55 60
               id="user-provide-bridge-connecting"
    
    56 61
               role="alert"
    
    57
    -          tabindex="0"
    
    58 62
               data-l10n-id="user-provide-bridge-dialog-connecting"
    
    59 63
             ></html:span>
    
    60 64
           </html:div>
    
    ... ... @@ -70,6 +74,8 @@
    70 74
             id="user-provide-bridge-grid-display"
    
    71 75
             class="tor-bridges-grid"
    
    72 76
             role="table"
    
    77
    +        tabindex="0"
    
    78
    +        aria-labelledby="user-provide-result-description"
    
    73 79
           ></html:div>
    
    74 80
           <html:template id="user-provide-bridge-row-template">
    
    75 81
             <html:div class="tor-bridges-grid-row" role="row">
    

  • browser/components/torpreferences/content/torPreferences.css
    ... ... @@ -14,6 +14,24 @@ button.spoof-button-disabled {
    14 14
       pointer-events: none;
    
    15 15
     }
    
    16 16
     
    
    17
    +.tor-loading-icon {
    
    18
    +  width: 16px;
    
    19
    +  height: 16px;
    
    20
    +  content: image-set(
    
    21
    +    url("chrome://global/skin/icons/tor-light-loading.png"),
    
    22
    +    url("chrome://global/skin/icons/tor-light-loading@2x.png") 2x
    
    23
    +  );
    
    24
    +}
    
    25
    +
    
    26
    +@media (prefers-color-scheme: dark) {
    
    27
    +  .tor-loading-icon {
    
    28
    +    content: image-set(
    
    29
    +      url("chrome://global/skin/icons/tor-dark-loading.png"),
    
    30
    +      url("chrome://global/skin/icons/tor-dark-loading@2x.png") 2x
    
    31
    +    );
    
    32
    +  }
    
    33
    +}
    
    34
    +
    
    17 35
     /* Status */
    
    18 36
     
    
    19 37
     #network-status-internet-area {
    
    ... ... @@ -81,21 +99,6 @@ button.spoof-button-disabled {
    81 99
     
    
    82 100
     .network-status-loading-icon {
    
    83 101
       margin-inline-end: 0.5em;
    
    84
    -  width: 16px;
    
    85
    -  height: 16px;
    
    86
    -  content: image-set(
    
    87
    -    url("chrome://global/skin/icons/tor-light-loading.png"),
    
    88
    -    url("chrome://global/skin/icons/tor-light-loading@2x.png") 2x
    
    89
    -  );
    
    90
    -}
    
    91
    -
    
    92
    -@media (prefers-color-scheme: dark) {
    
    93
    -  .network-status-loading-icon {
    
    94
    -    content: image-set(
    
    95
    -      url("chrome://global/skin/icons/tor-dark-loading.png"),
    
    96
    -      url("chrome://global/skin/icons/tor-dark-loading@2x.png") 2x
    
    97
    -    );
    
    98
    -  }
    
    99 102
     }
    
    100 103
     
    
    101 104
     #network-status-internet-area:not(.status-loading) .network-status-loading-icon {
    
    ... ... @@ -900,6 +903,8 @@ dialog#torPreferences-requestBridge-dialog > hbox {
    900 903
     #lox-invite-dialog-message-area {
    
    901 904
       grid-area: message;
    
    902 905
       justify-self: end;
    
    906
    +  display: flex;
    
    907
    +  align-items: center;
    
    903 908
     }
    
    904 909
     
    
    905 910
     #lox-invite-dialog-message-area::after {
    
    ... ... @@ -911,19 +916,29 @@ dialog#torPreferences-requestBridge-dialog > hbox {
    911 916
       color: var(--in-content-error-text-color);
    
    912 917
     }
    
    913 918
     
    
    914
    -#lox-invite-dialog-error-message:not(.show-error) {
    
    919
    +#lox-invite-dialog-generate-area:not(.show-error) #lox-invite-dialog-error-message {
    
    915 920
       display: none;
    
    916 921
     }
    
    917 922
     
    
    918 923
     #lox-invite-dialog-connecting {
    
    919 924
       color: var(--text-color-deemphasized);
    
    920
    -  /* TODO: Add spinner ::before */
    
    925
    +  /* Gap with #user-provide-bridge-loading-icon. */
    
    926
    +  margin-inline-start: 0.5em;
    
    921 927
     }
    
    922 928
     
    
    923
    -#lox-invite-dialog-connecting:not(.show-connecting) {
    
    929
    +#lox-invite-dialog-generate-area:not(.show-connecting) #lox-invite-dialog-connecting {
    
    924 930
       display: none;
    
    925 931
     }
    
    926 932
     
    
    933
    +#lox-invite-dialog-loading-icon {
    
    934
    +  flex: 0 0 auto;
    
    935
    +}
    
    936
    +
    
    937
    +#lox-invite-dialog-generate-area:not(.show-connecting) #lox-invite-dialog-loading-icon {
    
    938
    +  /* Use width:0 to effectively hide, but still occupy vertical space. */
    
    939
    +  width: 0;
    
    940
    +}
    
    941
    +
    
    927 942
     #lox-invite-dialog-list-label {
    
    928 943
       font-weight: 700;
    
    929 944
     }
    
    ... ... @@ -1019,6 +1034,8 @@ groupbox#torPreferences-bridges-group textarea {
    1019 1034
       flex: 0 0 auto;
    
    1020 1035
       margin-block: 8px 12px;
    
    1021 1036
       align-self: end;
    
    1037
    +  display: flex;
    
    1038
    +  align-items: center;
    
    1022 1039
     }
    
    1023 1040
     
    
    1024 1041
     #user-provide-bridge-message-area::after {
    
    ... ... @@ -1035,19 +1052,29 @@ groupbox#torPreferences-bridges-group textarea {
    1035 1052
       color: var(--in-content-error-text-color);
    
    1036 1053
     }
    
    1037 1054
     
    
    1038
    -#user-provide-bridge-error-message.not(.show-error) {
    
    1055
    +#user-provide-bridge-dialog:not(.show-error) #user-provide-bridge-error-message {
    
    1039 1056
       display: none;
    
    1040 1057
     }
    
    1041 1058
     
    
    1042 1059
     #user-provide-bridge-connecting {
    
    1043 1060
       color: var(--text-color-deemphasized);
    
    1044
    -  /* TODO: Add spinner ::before */
    
    1061
    +  /* Gap with #user-provide-bridge-loading-icon. */
    
    1062
    +  margin-inline-start: 0.5em;
    
    1045 1063
     }
    
    1046 1064
     
    
    1047
    -#user-provide-bridge-connecting:not(.show-connecting) {
    
    1065
    +#user-provide-bridge-dialog:not(.show-connecting) #user-provide-bridge-connecting {
    
    1048 1066
       display: none;
    
    1049 1067
     }
    
    1050 1068
     
    
    1069
    +#user-provide-bridge-loading-icon {
    
    1070
    +  flex: 0 0 auto;
    
    1071
    +}
    
    1072
    +
    
    1073
    +#user-provide-bridge-dialog:not(.show-connecting) #user-provide-bridge-loading-icon {
    
    1074
    +  /* Use width:0 to effectively hide, but still occupy vertical space. */
    
    1075
    +  width: 0;
    
    1076
    +}
    
    1077
    +
    
    1051 1078
     #user-provide-bridge-result-page {
    
    1052 1079
       flex: 1 1 0;
    
    1053 1080
       min-height: 0;
    
    ... ... @@ -1065,6 +1092,11 @@ groupbox#torPreferences-bridges-group textarea {
    1065 1092
       margin-block: 8px;
    
    1066 1093
     }
    
    1067 1094
     
    
    1095
    +#user-provide-bridge-grid-display:focus-visible {
    
    1096
    +  outline: var(--in-content-focus-outline);
    
    1097
    +  outline-offset: var(--in-content-focus-outline-offset);
    
    1098
    +}
    
    1099
    +
    
    1068 1100
     /* Connection settings dialog */
    
    1069 1101
     #torPreferences-connection-dialog label {
    
    1070 1102
       /* Do not wrap the labels. */