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

[tor-commits] [Git][tpo/applications/tor-browser][tor-browser-128.8.0esr-14.5-1] 2 commits: fixup! TB 27476: Implement about:torconnect captive portal within Tor Browser



Title: GitLab

morgan pushed to branch tor-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser

Commits:

  • 36916036
    by Henry Wilkes at 2025-03-17T21:14:23+00:00
    fixup! TB 27476: Implement about:torconnect captive portal within Tor Browser
    
    TB 43321: Only focus the about:torconnect buttons under certain
    circumstances.
    
    By default, when switching stages we move the focus back to the stage
    heading. This is because we want to lead the user back to the top of the
    page to show them the new context. This should help improve the
    experience when using a screen reader.
    
    If we are in the bootstrapping stage we instead move the focus to the
    "Cancel" button since it is likely that the user wants to use this
    control.
    
    If the user presses the "Cancel" button we return the focus to the
    "Connect" or "Try a bridge" button. I.e. we restore the prior focus.
    This allows to user to easily re-try without having to re-read the page
    they just saw.
    
    We do a similar thing when the user cancels the automatic startup
    bootstrapping.
    
    Finally, on page load we will focus the "Connect" button if the user has
    previously interacted with it. We record this interaction in a
    preference that persists between sessions.
    
    We also separate out the "Loading" stage from the "Start" stage. It is
    unexpected for `about:torconnect` to be opened whilst in the "Loading"
    stage, but if it does happen it would be safer to keep the page blank.
    The way this is implemented also ensures that the initial page is blank
    prior to "get-init-args" resolving.
    
  • ad21bdd6
    by Henry Wilkes at 2025-03-17T21:14:23+00:00
    fixup! TB 40597: Implement TorSettings module
    
    TB 43321: Add a isQuickstart property to the TorConnect.stage.
    
    This is used by `about:torconnect` for determining focus behaviour.
    

5 changed files:

Changes:

  • toolkit/components/torconnect/TorConnectParent.sys.mjs
    ... ... @@ -13,6 +13,9 @@ ChromeUtils.defineESModuleGetters(lazy, {
    13 13
       HomePage: "resource:///modules/HomePage.sys.mjs",
    
    14 14
     });
    
    15 15
     
    
    16
    +const userHasEverClickedConnectPref =
    
    17
    +  "torbrowser.about_torconnect.user_has_ever_clicked_connect";
    
    18
    +
    
    16 19
     /*
    
    17 20
     This object is basically a marshalling interface between the TorConnect module
    
    18 21
     and a particular about:torconnect page
    
    ... ... @@ -117,6 +120,9 @@ export class TorConnectParent extends JSWindowActorParent {
    117 120
             TorConnect.chooseRegion();
    
    118 121
             break;
    
    119 122
           case "torconnect:begin-bootstrapping":
    
    123
    +        if (message.data.userClickedConnect) {
    
    124
    +          Services.prefs.setBoolPref(userHasEverClickedConnectPref, true);
    
    125
    +        }
    
    120 126
             TorConnect.beginBootstrapping(message.data.regionCode);
    
    121 127
             break;
    
    122 128
           case "torconnect:cancel-bootstrapping":
    
    ... ... @@ -130,6 +136,10 @@ export class TorConnectParent extends JSWindowActorParent {
    130 136
               Direction: Services.locale.isAppLocaleRTL ? "rtl" : "ltr",
    
    131 137
               CountryNames: TorConnect.countryNames,
    
    132 138
               stage: TorConnect.stage,
    
    139
    +          userHasEverClickedConnect: Services.prefs.getBoolPref(
    
    140
    +            userHasEverClickedConnectPref,
    
    141
    +            false
    
    142
    +          ),
    
    133 143
               quickstartEnabled: TorConnect.quickstart,
    
    134 144
             };
    
    135 145
           case "torconnect:get-frequent-regions":
    

  • toolkit/components/torconnect/content/aboutTorConnect.css
    ... ... @@ -15,6 +15,11 @@ html {
    15 15
       height: 100%;
    
    16 16
     }
    
    17 17
     
    
    18
    +body:not(.loaded) {
    
    19
    +  /* Keep blank whilst loading. */
    
    20
    +  display: none;
    
    21
    +}
    
    22
    +
    
    18 23
     #breadcrumbs {
    
    19 24
       display: flex;
    
    20 25
       align-items: center;
    
    ... ... @@ -93,6 +98,11 @@ html {
    93 98
       display: none;
    
    94 99
     }
    
    95 100
     
    
    101
    +#tor-connect-heading {
    
    102
    +  /* Do not show the focus outline. */
    
    103
    +  outline: none;
    
    104
    +}
    
    105
    +
    
    96 106
     #connect-to-tor {
    
    97 107
       margin-inline-start: 0;
    
    98 108
     }
    

  • toolkit/components/torconnect/content/aboutTorConnect.html
    ... ... @@ -50,7 +50,7 @@
    50 50
           </div>
    
    51 51
           <div id="text-container">
    
    52 52
             <div class="title">
    
    53
    -          <h1 class="title-text"></h1>
    
    53
    +          <h1 id="tor-connect-heading" class="title-text" tabindex="-1"></h1>
    
    54 54
             </div>
    
    55 55
             <div id="connectLongContent">
    
    56 56
               <p id="connectLongContentText"></p>
    

  • toolkit/components/torconnect/content/aboutTorConnect.js
    ... ... @@ -32,7 +32,6 @@ class AboutTorConnect {
    32 32
       selectors = Object.freeze({
    
    33 33
         textContainer: {
    
    34 34
           title: "div.title",
    
    35
    -      titleText: "h1.title-text",
    
    36 35
           longContentText: "#connectLongContentText",
    
    37 36
         },
    
    38 37
         progress: {
    
    ... ... @@ -77,7 +76,7 @@ class AboutTorConnect {
    77 76
     
    
    78 77
       elements = Object.freeze({
    
    79 78
         title: document.querySelector(this.selectors.textContainer.title),
    
    80
    -    titleText: document.querySelector(this.selectors.textContainer.titleText),
    
    79
    +    heading: document.getElementById("tor-connect-heading"),
    
    81 80
         longContentText: document.querySelector(
    
    82 81
           this.selectors.textContainer.longContentText
    
    83 82
         ),
    
    ... ... @@ -138,18 +137,44 @@ class AboutTorConnect {
    138 137
     
    
    139 138
       locations = {};
    
    140 139
     
    
    141
    -  beginBootstrapping() {
    
    142
    -    RPMSendAsyncMessage("torconnect:begin-bootstrapping", {});
    
    140
    +  /**
    
    141
    +   * Whether the user requested a cancellation of the bootstrap from *this*
    
    142
    +   * page.
    
    143
    +   *
    
    144
    +   * @type {boolean}
    
    145
    +   */
    
    146
    +  userCancelled = false;
    
    147
    +
    
    148
    +  /**
    
    149
    +   * Start a normal bootstrap attempt.
    
    150
    +   *
    
    151
    +   * @param {boolean} userClickedConnect - Whether this request was triggered by
    
    152
    +   *   the user clicking the "Connect" button on the "Start" page.
    
    153
    +   */
    
    154
    +  beginBootstrapping(userClickedConnect) {
    
    155
    +    RPMSendAsyncMessage("torconnect:begin-bootstrapping", {
    
    156
    +      userClickedConnect,
    
    157
    +    });
    
    143 158
       }
    
    144 159
     
    
    160
    +  /**
    
    161
    +   * Start an auto bootstrap attempt.
    
    162
    +   *
    
    163
    +   * @param {string} regionCode - The region code to use for the bootstrap, or
    
    164
    +   *   "automatic".
    
    165
    +   */
    
    145 166
       beginAutoBootstrapping(regionCode) {
    
    146 167
         RPMSendAsyncMessage("torconnect:begin-bootstrapping", {
    
    147 168
           regionCode,
    
    148 169
         });
    
    149 170
       }
    
    150 171
     
    
    172
    +  /**
    
    173
    +   * Try and cancel the current bootstrap attempt.
    
    174
    +   */
    
    151 175
       cancelBootstrapping() {
    
    152 176
         RPMSendAsyncMessage("torconnect:cancel-bootstrapping");
    
    177
    +    this.userCancelled = true;
    
    153 178
       }
    
    154 179
     
    
    155 180
       /*
    
    ... ... @@ -260,7 +285,7 @@ class AboutTorConnect {
    260 285
       }
    
    261 286
     
    
    262 287
       setTitle(title, className) {
    
    263
    -    this.elements.titleText.textContent = title;
    
    288
    +    this.elements.heading.textContent = title;
    
    264 289
         this.elements.title.className = "title";
    
    265 290
         if (className) {
    
    266 291
           this.elements.title.classList.add(className);
    
    ... ... @@ -349,18 +374,88 @@ class AboutTorConnect {
    349 374
         }
    
    350 375
       }
    
    351 376
     
    
    377
    +  /**
    
    378
    +   * The connect button that was focused just prior to a bootstrap attempt, if
    
    379
    +   * any.
    
    380
    +   *
    
    381
    +   * @type {?Element}
    
    382
    +   */
    
    383
    +  preBootstrappingFocus = null;
    
    384
    +
    
    385
    +  /**
    
    386
    +   * The stage that was shown on this page just prior to a bootstrap attempt.
    
    387
    +   *
    
    388
    +   * @type {?string}
    
    389
    +   */
    
    390
    +  preBootstrappingStage = null;
    
    391
    +
    
    352 392
       /*
    
    353 393
       These methods update the UI based on the current TorConnect state
    
    354 394
       */
    
    355 395
     
    
    356
    -  updateStage(stage) {
    
    396
    +  /**
    
    397
    +   * Update the shown stage.
    
    398
    +   *
    
    399
    +   * @param {ConnectStage} stage - The new stage to show.
    
    400
    +   * @param {boolean} [focusConnect=false] - Whether to try and focus the
    
    401
    +   *   connect button, if we are in the Start stage.
    
    402
    +   */
    
    403
    +  updateStage(stage, focusConnect = false) {
    
    357 404
         if (stage.name === this.shownStage) {
    
    358 405
           return;
    
    359 406
         }
    
    360 407
     
    
    408
    +    const prevStage = this.shownStage;
    
    361 409
         this.shownStage = stage.name;
    
    362 410
         this.selectedLocation = stage.defaultRegion;
    
    363 411
     
    
    412
    +    // By default we want to reset the focus to the top of the page when
    
    413
    +    // changing the displayed page since we want a user to read the new page
    
    414
    +    // before activating a control.
    
    415
    +    let moveFocus = this.elements.heading;
    
    416
    +
    
    417
    +    if (stage.name === "Bootstrapping") {
    
    418
    +      this.preBootstrappingStage = prevStage;
    
    419
    +      this.preBootstrappingFocus = null;
    
    420
    +      if (focusConnect && stage.isQuickstart) {
    
    421
    +        // If this is the initial automatic bootstrap triggered by the
    
    422
    +        // quickstart preference, treat as if the previous shown stage was
    
    423
    +        // "Start" and the user clicked the "Connect" button.
    
    424
    +        // Then, if the user cancels, the focus should still move to the
    
    425
    +        // "Connect" button.
    
    426
    +        this.preBootstrappingStage = "Start";
    
    427
    +        this.preBootstrappingFocus = this.elements.connectButton;
    
    428
    +      } else if (this.elements.connectButton.contains(document.activeElement)) {
    
    429
    +        this.preBootstrappingFocus = this.elements.connectButton;
    
    430
    +      } else if (
    
    431
    +        this.elements.tryBridgeButton.contains(document.activeElement)
    
    432
    +      ) {
    
    433
    +        this.preBootstrappingFocus = this.elements.tryBridgeButton;
    
    434
    +      }
    
    435
    +    } else {
    
    436
    +      if (
    
    437
    +        this.userCancelled &&
    
    438
    +        prevStage === "Bootstrapping" &&
    
    439
    +        stage.name === this.preBootstrappingStage &&
    
    440
    +        this.preBootstrappingFocus &&
    
    441
    +        this.elements.cancelButton.contains(document.activeElement)
    
    442
    +      ) {
    
    443
    +        // If returning back to the same stage after the user tried to cancel
    
    444
    +        // bootstrapping from within this page, then we restore the focus to the
    
    445
    +        // connect button to allow the user to quickly re-try.
    
    446
    +        // If the bootstrap was cancelled for any other reason, we reset the
    
    447
    +        // focus as usual.
    
    448
    +        moveFocus = this.preBootstrappingFocus;
    
    449
    +      }
    
    450
    +      // Clear the Bootstrapping variables.
    
    451
    +      this.preBootstrappingStage = null;
    
    452
    +      this.preBootstrappingFocus = null;
    
    453
    +    }
    
    454
    +
    
    455
    +    // Clear the recording of the cancellation request.
    
    456
    +    this.userCancelled = false;
    
    457
    +
    
    458
    +    let isLoaded = true;
    
    364 459
         let showProgress = false;
    
    365 460
         let showLog = false;
    
    366 461
         switch (stage.name) {
    
    ... ... @@ -368,14 +463,21 @@ class AboutTorConnect {
    368 463
             console.error("Should not be open when TorConnect is disabled");
    
    369 464
             break;
    
    370 465
           case "Loading":
    
    466
    +        // Unexpected for this page to open so early.
    
    467
    +        console.warn("Page opened whilst loading");
    
    468
    +        isLoaded = false;
    
    469
    +        break;
    
    371 470
           case "Start":
    
    372
    -        // Loading is not currnetly handled, treat the same as "Start", but UI
    
    373
    -        // will be unresponsive.
    
    374 471
             this.showStart(stage.tryAgain, stage.potentiallyBlocked);
    
    472
    +        if (focusConnect) {
    
    473
    +          moveFocus = this.elements.connectButton;
    
    474
    +        }
    
    375 475
             break;
    
    376 476
           case "Bootstrapping":
    
    377 477
             showProgress = true;
    
    378 478
             this.showBootstrapping(stage.bootstrapTrigger, stage.tryAgain);
    
    479
    +        // Always focus the cancel button.
    
    480
    +        moveFocus = this.elements.cancelButton;
    
    379 481
             break;
    
    380 482
           case "Offline":
    
    381 483
             showLog = true;
    
    ... ... @@ -419,6 +521,9 @@ class AboutTorConnect {
    419 521
         } else {
    
    420 522
           this.hide(this.elements.viewLogButton);
    
    421 523
         }
    
    524
    +
    
    525
    +    document.body.classList.toggle("loaded", isLoaded);
    
    526
    +    moveFocus.focus();
    
    422 527
       }
    
    423 528
     
    
    424 529
       updateBootstrappingStatus(data) {
    
    ... ... @@ -452,10 +557,9 @@ class AboutTorConnect {
    452 557
         this.show(this.elements.quickstartContainer);
    
    453 558
         this.show(this.elements.configureButton);
    
    454 559
         this.show(this.elements.connectButton, true);
    
    455
    -    this.elements.connectButton.focus();
    
    456
    -    if (tryAgain) {
    
    457
    -      this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
    
    458
    -    }
    
    560
    +    this.elements.connectButton.textContent = tryAgain
    
    561
    +      ? TorStrings.torConnect.tryAgain
    
    562
    +      : TorStrings.torConnect.torConnectButton;
    
    459 563
         if (potentiallyBlocked) {
    
    460 564
           this.setBreadcrumbsStatus(
    
    461 565
             BreadcrumbStatus.Active,
    
    ... ... @@ -511,7 +615,6 @@ class AboutTorConnect {
    511 615
         }
    
    512 616
         this.hideButtons();
    
    513 617
         this.show(this.elements.cancelButton);
    
    514
    -    this.elements.cancelButton.focus();
    
    515 618
       }
    
    516 619
     
    
    517 620
       showOffline() {
    
    ... ... @@ -541,7 +644,6 @@ class AboutTorConnect {
    541 644
           BreadcrumbStatus.Disabled
    
    542 645
         );
    
    543 646
         this.showLocationForm(true, TorStrings.torConnect.tryBridge);
    
    544
    -    this.elements.tryBridgeButton.focus();
    
    545 647
       }
    
    546 648
     
    
    547 649
       showRegionNotFound() {
    
    ... ... @@ -557,7 +659,6 @@ class AboutTorConnect {
    557 659
           BreadcrumbStatus.Disabled
    
    558 660
         );
    
    559 661
         this.showLocationForm(false, TorStrings.torConnect.tryBridge);
    
    560
    -    this.elements.tryBridgeButton.focus();
    
    561 662
       }
    
    562 663
     
    
    563 664
       showConfirmRegion(error) {
    
    ... ... @@ -573,7 +674,6 @@ class AboutTorConnect {
    573 674
           BreadcrumbStatus.Active
    
    574 675
         );
    
    575 676
         this.showLocationForm(false, TorStrings.torConnect.tryAgain);
    
    576
    -    this.elements.tryBridgeButton.focus();
    
    577 677
       }
    
    578 678
     
    
    579 679
       showFinalError(error) {
    
    ... ... @@ -722,7 +822,8 @@ class AboutTorConnect {
    722 822
         this.elements.connectButton.textContent =
    
    723 823
           TorStrings.torConnect.torConnectButton;
    
    724 824
         this.elements.connectButton.addEventListener("click", () => {
    
    725
    -      this.beginBootstrapping();
    
    825
    +      // Record as userClickedConnect if we are in the Start stage.
    
    826
    +      this.beginBootstrapping(this.shownStage === "Start");
    
    726 827
         });
    
    727 828
     
    
    728 829
         this.populateLocations();
    
    ... ... @@ -802,7 +903,13 @@ class AboutTorConnect {
    802 903
         this.initObservers();
    
    803 904
         this.initKeyboardShortcuts();
    
    804 905
     
    
    805
    -    this.updateStage(args.stage);
    
    906
    +    // If we have previously opened about:torconnect and the user tried the
    
    907
    +    // "Connect" button we want to focus the "Connect" button for easy
    
    908
    +    // activation.
    
    909
    +    // Otherwise, we do not want to focus it for first time users so they can
    
    910
    +    // read the full page first.
    
    911
    +    const focusConnect = args.userHasEverClickedConnect;
    
    912
    +    this.updateStage(args.stage, focusConnect);
    
    806 913
         this.updateQuickstart(args.quickstartEnabled);
    
    807 914
       }
    
    808 915
     }
    

  • toolkit/modules/TorConnect.sys.mjs
    ... ... @@ -680,10 +680,14 @@ export const TorConnectStage = Object.freeze({
    680 680
      * A summary of the user stage.
    
    681 681
      * (This class is mirrored for Android in TorConnectStage.java. Changes should be mirrored there)
    
    682 682
      *
    
    683
    - * @property {string} name - The name of the stage.
    
    683
    + * @property {string} name - The name of the stage. One of the values in
    
    684
    + *   `TorConnectStage`.
    
    684 685
      * @property {string} defaultRegion - The default region to show in the UI.
    
    685
    - * @property {?string} bootstrapTrigger - The TorConnectStage prior to this
    
    686
    + * @property {?string} bootstrapTrigger - The name of the stage prior to this
    
    686 687
      *   bootstrap attempt. Only set during the "Bootstrapping" stage.
    
    688
    + * @property {boolean} isQuickstart - Whether the current bootstrap attempt was
    
    689
    + *   triggered by the `TorConnect.quickstart` preference. Will be `false`
    
    690
    + *   outside of the "Bootstrapping" stage.
    
    687 691
      * @property {?BootstrapError} error - The last bootstrapping error.
    
    688 692
      * @property {boolean} tryAgain - Whether a bootstrap attempt has failed, so
    
    689 693
      *   that a normal bootstrap should be shown as "Try Again" instead of
    
    ... ... @@ -752,6 +756,14 @@ export const TorConnect = {
    752 756
        */
    
    753 757
       _bootstrapTrigger: null,
    
    754 758
     
    
    759
    +  /**
    
    760
    +   * Whether the current bootstrapping attempt was triggered by the quickstart
    
    761
    +   * preference.
    
    762
    +   *
    
    763
    +   * @type {boolean}
    
    764
    +   */
    
    765
    +  _isQuickstart: false,
    
    766
    +
    
    755 767
       /**
    
    756 768
        * The alternative stage that we should move to after bootstrapping completes.
    
    757 769
        *
    
    ... ... @@ -807,6 +819,7 @@ export const TorConnect = {
    807 819
           name: this._stageName,
    
    808 820
           defaultRegion: this._defaultRegion,
    
    809 821
           bootstrapTrigger: this._bootstrapTrigger,
    
    822
    +      isQuickstart: this._isQuickstart,
    
    810 823
           error: this._errorDetails
    
    811 824
             ? {
    
    812 825
                 code: this._errorDetails.code,
    
    ... ... @@ -935,7 +948,7 @@ export const TorConnect = {
    935 948
           // And the previous bootstrap attempt must have succeeded.
    
    936 949
           !Services.prefs.getBoolPref(TorConnectPrefs.prompt_at_startup, true)
    
    937 950
         ) {
    
    938
    -      this.beginBootstrapping();
    
    951
    +      this._beginBootstrappingInternal(undefined, true);
    
    939 952
         }
    
    940 953
       },
    
    941 954
     
    
    ... ... @@ -1303,6 +1316,19 @@ export const TorConnect = {
    1303 1316
        *   an auto-bootstrap attempt.
    
    1304 1317
        */
    
    1305 1318
       async beginBootstrapping(regionCode) {
    
    1319
    +    await this._beginBootstrappingInternal(regionCode, false);
    
    1320
    +  },
    
    1321
    +
    
    1322
    +  /**
    
    1323
    +   * Begin a bootstrap attempt.
    
    1324
    +   *
    
    1325
    +   * @param {string} [regionCode] - An optional region code string to use, or
    
    1326
    +   *   "automatic" to automatically determine the region. If given, will start
    
    1327
    +   *   an auto-bootstrap attempt.
    
    1328
    +   * @param {boolean} isQuickstart - Whether this was triggered by the
    
    1329
    +   *   quickstart option.
    
    1330
    +   */
    
    1331
    +  async _beginBootstrappingInternal(regionCode, isQuickstart) {
    
    1306 1332
         lazy.logger.debug("TorConnect.beginBootstrapping()");
    
    1307 1333
     
    
    1308 1334
         if (!this._confirmBootstrapping(regionCode)) {
    
    ... ... @@ -1331,6 +1357,7 @@ export const TorConnect = {
    1331 1357
         }
    
    1332 1358
         this._requestedStage = null;
    
    1333 1359
         this._bootstrapTrigger = beginStage;
    
    1360
    +    this._isQuickstart = isQuickstart;
    
    1334 1361
         this._setStage(TorConnectStage.Bootstrapping);
    
    1335 1362
         this._bootstrapAttempt = bootstrapAttempt;
    
    1336 1363
     
    
    ... ... @@ -1349,6 +1376,7 @@ export const TorConnect = {
    1349 1376
         const requestedStage = this._requestedStage;
    
    1350 1377
         this._requestedStage = null;
    
    1351 1378
         this._bootstrapTrigger = null;
    
    1379
    +    this._isQuickstart = false;
    
    1352 1380
         this._bootstrapAttempt = null;
    
    1353 1381
     
    
    1354 1382
         if (bootstrapAttempt.detectedRegion) {
    

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