richard pushed to branch tor-browser-115.10.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
-
00e009df
by Henry Wilkes at 2024-04-18T13:57:12+01:00
6 changed files:
- browser/components/torpreferences/content/connectionPane.xhtml
- browser/components/torpreferences/content/loxInviteDialog.js
- browser/components/torpreferences/content/loxInviteDialog.xhtml
- browser/components/torpreferences/content/provideBridgeDialog.js
- browser/components/torpreferences/content/provideBridgeDialog.xhtml
- browser/components/torpreferences/content/torPreferences.css
Changes:
| ... | ... | @@ -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
|
| ... | ... | @@ -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;
|
| ... | ... | @@ -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>
|
| ... | ... | @@ -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;
|
| ... | ... | @@ -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">
|
| ... | ... | @@ -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. */
|