Commit 192762bf authored by henry's avatar henry Committed by Pier Angelo Vendrame
Browse files

fixup! TB 27476: Implement about:torconnect captive portal within Tor Browser

TB 43186: Add a `"ProviderStopped"` page to about:torconnect.
parent 47bb646f
Loading
Loading
Loading
Loading
+22 −3
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
import { TorStrings } from "moz-src:///toolkit/modules/TorStrings.sys.mjs";
import {
  TorConnect,
  TorConnectStage,
  TorConnectTopics,
} from "moz-src:///toolkit/modules/TorConnect.sys.mjs";

@@ -26,9 +27,7 @@ and a particular about:torconnect page
 * It adapts and relays the messages from and to the TorConnect module.
 */
export class TorConnectParent extends JSWindowActorParent {
  constructor(...args) {
    super(...args);

  actorCreated() {
    const self = this;

    // JSWindowActiveParent derived objects cannot observe directly, so create a
@@ -44,6 +43,9 @@ export class TorConnectParent extends JSWindowActorParent {
          case TorConnectTopics.StageChange:
            self.sendAsyncMessage("torconnect:stage-change", obj);
            break;
          case TorConnectTopics.ProviderStatusChange:
            self.sendAsyncMessage("torconnect:provider-status-change", obj);
            break;
          case TorConnectTopics.BootstrapProgress:
            self.sendAsyncMessage("torconnect:bootstrap-progress", obj);
            break;
@@ -64,6 +66,10 @@ export class TorConnectParent extends JSWindowActorParent {
      this.torConnectObserver,
      TorConnectTopics.StageChange
    );
    Services.obs.addObserver(
      this.torConnectObserver,
      TorConnectTopics.ProviderStatusChange
    );
    Services.obs.addObserver(
      this.torConnectObserver,
      TorConnectTopics.BootstrapProgress
@@ -124,6 +130,9 @@ export class TorConnectParent extends JSWindowActorParent {
          Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit
        );
        break;
      case "torconnect:restart-provider":
        TorConnect.restartProvider();
        break;
      case "torconnect:start-again":
        TorConnect.startAgain();
        break;
@@ -267,3 +276,13 @@ export class TorConnectParent extends JSWindowActorParent {
    );
  }
}

Services.obs.addObserver((_subject, topic) => {
  if (topic !== TorConnectTopics.StageChange) {
    return;
  }
  if (TorConnect.stageName === TorConnectStage.ProviderStopped) {
    // Make sure we have an open tab to show the ProviderStopped stage.
    TorConnectParent.open();
  }
}, TorConnectTopics.StageChange);
+57 −18
Original line number Diff line number Diff line
@@ -235,40 +235,57 @@ form#locationDropdown select {
  stroke: var(--icon-color);
  /* Make non-interactive (non-draggable). */
  pointer-events: none;
}

.torconnect-icon.offline-icon {
  &.offline-icon {
    content: url("chrome://browser/content/torconnect/network-broken.svg");
  }

.torconnect-icon.assist-icon {
  &.assist-icon {
    content: url("chrome://browser/content/torconnect/tor-connect-broken.svg");
  }

.torconnect-icon.location-icon {
  &.location-icon {
    content: url("chrome://browser/content/torconnect/connection-location.svg");
    stroke: var(--icon-color-warning);
  }
}

.torconnect-heading {
  grid-area: heading;
  /* Do not show the focus outline. */
  outline: none;
  font-size: var(--font-size-xxxlarge);
  font-size: var(--font-size-xxlarge);
  margin: 0;
  margin-block-end: var(--space-xxlarge);
}

.torconnect-text-container {
  grid-area: text-container;

  & > *:not(:last-child) {
    margin-block-end: var(--space-large);
  }

.torconnect-text-container p {
  & :is(p, ul) {
    margin: 0;
  }

.torconnect-text-container > *:not(:last-child) {
  margin-block-end: var(--space-large);
  & ul {
    padding-inline-start: var(--space-xxlarge);
  }

  & li:not(:last-child) {
    margin-block-end: var(--space-medium);
  }
}

.torconnect-list-intro {
  font-weight: var(--font-weight-semibold);
}

body.hide-provider-config-debugging .provider-config-debugging,
body:not(.hide-provider-config-debugging) .provider-generic-debugging {
  display: none;
}

.torconnect-controls-container {
@@ -281,12 +298,34 @@ form#locationDropdown select {
  align-items: center;
  /* Gap between children and rows. */
  gap: var(--space-small) var(--space-small);
}

.torconnect-controls-container > * {
  & > * {
    flex: 0 0 max-content;
    margin: 0;
  }
}

#restart-provider-failed {
  color: var(--text-color-error);
  align-items: center;
  gap: var(--space-xxsmall);

  &:not([hidden]) {
    display: flex;
  }

  & > * {
    flex: 0 0 max-content;
  }
}

#restart-provider-failed-icon {
  width: var(--icon-size);
  height: var(--icon-size);
  -moz-context-properties: fill;
  fill: var(--icon-color-critical);
  content: url("chrome://global/skin/icons/error.svg");
}

/* Temporary hacks for the connectPageContainer. */
#connectPageContainer .torconnect-text-container > *:last-child {
+90 −0
Original line number Diff line number Diff line
@@ -15,12 +15,22 @@
      href="chrome://browser/content/torconnect/aboutTorConnect.css"
    />

    <link rel="localization" href="branding/brand.ftl" />
    <link rel="localization" href="toolkit/global/tor-browser.ftl" />
    <link rel="localization" href="toolkit/global/mozSupportLink.ftl" />

    <script
      type="module"
      src="chrome://global/content/elements/moz-button.mjs"
    ></script>
    <script
      type="module"
      src="chrome://global/content/elements/moz-toggle.mjs"
    ></script>
    <script
      type="module"
      src="chrome://global/content/elements/moz-support-link.mjs"
    ></script>
  </head>
  <body class="onion-pattern-background">
    <div id="progressBar" hidden="hidden">
@@ -50,6 +60,86 @@
        <span class="breadcrumb-label"></span>
      </span>
    </div>
    <div class="torconnect-stage-content" data-stage-name="ProviderStopped">
      <img class="torconnect-icon assist-icon" alt="" />
      <h1
        class="torconnect-heading"
        tabindex="-1"
        data-l10n-id="tor-connect-tor-not-working-heading"
      ></h1>
      <div class="torconnect-text-container">
        <p data-l10n-id="tor-connect-tor-not-working-intro"></p>
        <p
          class="torconnect-list-intro"
          data-l10n-id="tor-connect-tor-not-working-cause-intro"
        ></p>
        <ul>
          <li
            class="provider-config-debugging"
            data-l10n-id="tor-connect-tor-not-working-cause-applications"
          ></li>
          <li
            class="provider-config-debugging"
            data-l10n-id="tor-connect-tor-not-working-cause-files"
          ></li>
          <li
            class="provider-config-debugging"
            data-l10n-id="tor-connect-tor-not-working-cause-other"
          ></li>
          <li
            class="provider-generic-debugging"
            data-l10n-id="tor-connect-tor-not-working-cause-generic"
          ></li>
        </ul>
        <p
          class="torconnect-list-intro"
          data-l10n-id="tor-connect-tor-not-working-resolve-intro"
        ></p>
        <ul>
          <li
            class="provider-config-debugging"
            data-l10n-id="tor-connect-tor-not-working-resolve-applications"
          ></li>
          <li
            class="provider-config-debugging"
            data-l10n-id="tor-connect-tor-not-working-resolve-files"
          ></li>
          <li
            class="provider-config-debugging"
            data-l10n-id="tor-connect-tor-not-working-resolve-restart-long"
          ></li>
          <li
            class="provider-generic-debugging"
            data-l10n-id="tor-connect-tor-not-working-resolve-restart-short"
          ></li>
        </ul>
        <p data-l10n-id="tor-connect-tor-not-working-support">
          <a
            is="moz-support-link"
            data-l10n-name="support-link"
            support-page="tor-manual:get-in-touch__bug-or-feedback"
          >
          </a>
        </p>
        <a
          is="moz-support-link"
          support-page="tor-manual:encountering-issues__troubleshooting"
        ></a>
      </div>
      <div class="torconnect-controls-container">
        <div id="restart-provider-failed" role="alert">
          <img id="restart-provider-failed-icon" alt="" />
          <span
            data-l10n-id="tor-connect-tor-not-working-restarting-failed"
          ></span>
        </div>
        <moz-button
          id="restart-provider-button"
          type="primary"
          data-l10n-id="tor-connect-tor-not-working-restart-button"
        ></moz-button>
      </div>
    </div>
    <div id="connectPageContainer" class="torconnect-stage-content">
      <img id="tor-connect-icon" class="torconnect-icon" alt="" />
      <h1
+168 −0
Original line number Diff line number Diff line
@@ -134,6 +134,8 @@ class AboutTorConnect {
      "full-regions-option-group"
    ),
    tryBridgeButton: document.querySelector(this.selectors.buttons.tryBridge),
    restartProviderButton: document.getElementById("restart-provider-button"),
    restartProviderFailed: document.getElementById("restart-provider-failed"),
  });

  /**
@@ -418,6 +420,10 @@ class AboutTorConnect {
        console.warn("Page opened whilst loading");
        isLoaded = false;
        break;
      case "ProviderStopped":
        this.hideBreadcrumbs();
        this.updateProviderStatus(stage.providerStatus, true);
        break;
      case "Start":
        this.showStart(stage.tryAgain, stage.potentiallyBlocked);
        if (focusConnect) {
@@ -479,6 +485,157 @@ class AboutTorConnect {
    moveFocus.focus();
  }

  /**
   * The stored data for the `ProviderStopped` page content. `null` whilst
   * uninitialized.
   *
   * @type {object}
   */
  _providerStoppedData = null;

  /**
   * Called whenever the tor provider status may have changed. Only relevant in
   * the `ProviderStopped` stage.
   *
   * @param {ProviderStatus} providerStatus - The latest provider status.
   * @param {boolean} enteringStage - Whether this is being called because we
   *   have just entered the `ProviderStopped` stage as the initial stage of the
   *   page, or transitioning from another stage.
   */
  updateProviderStatus(providerStatus, enteringStage) {
    if (this.shownStage !== "ProviderStopped") {
      // Ignore.
      return;
    }
    if (enteringStage) {
      // Reset the data:
      // + When entering the stage.
      // + When opening the page in this initial stage.
      this._providerStoppedData = {
        // Set to `null` to make sure we don't early exit below.
        isRestarting: null,
        forceShowRestarting: false,
        canShowFailedAlert: false,
        // We set the maybeConfigIssue when entering the stage, but do not attempt
        // to update it between failed attempts.
        maybeConfigIssue: providerStatus.maybeConfigIssue,
      };
    }

    // We show the UI as restarting if the state is not "Stopped", which
    // includes both "Starting" and "Running". In the latter case (which is
    // unexpected) we still want to show the state as loading before we enter a
    // different shownStage.
    const restarting = providerStatus.state !== "Stopped";

    const data = this._providerStoppedData;

    if (restarting === data.isRestarting) {
      // No change in the recorded state. Don't change the UI or adjust any
      // timeouts.
      return;
    }

    data.isRestarting = restarting;
    if (restarting) {
      // Whilst we remain in this `ProviderStopped` stage, we will now show the
      // failed alert when we re-enter the "Stopped" state.
      data.canShowFailedAlert = true;

      if (!data.forceShowRestarting) {
        // Force the restarting UI to show for at least a certain amount of
        // time. Even if we fail early, we continue to show the restarting UI
        // for a short time to make sure the user has enough time to visually
        // see the restarting UI before we return to the error state.
        data.forceShowRestarting = true;
        setTimeout(() => {
          if (
            data !== this._providerStoppedData ||
            this.shownStage !== "ProviderStopped"
          ) {
            // We left or re-entered this stage whilst waiting. Do nothing.
            return;
          }
          data.forceShowRestarting = false;
          this.updateProviderStoppedContent();
        }, 1000);
      }
      // Else, we already have a pending timeout.
      // E.g., consider the following timeline, where "R" represents entering
      // the "Starting" state, "S" represents entering the "Stopped" state,
      // "T0" represents the start of the forcedShowRestarting timeout, and "T1"
      // represents the completion:
      //
      // |--------------+-----+-----+-----+----+------------------>
      // S0             R0    S1    R1    S2
      //                T0                     T1
      // |--stopped UI--|--restarting UI-------|--stopped UI-------
      //
      //
      // Similarly with S2 delayed:
      //
      // |--------------+-----+-----+----------+----+------------->
      // S0             R0    S1    R1              S2
      //                T0                     T1
      // |--stopped UI--|--restarting UI------------|--stopped UI--
      //
      //
      // I.e. we restart once (R0) which starts the timeout. Then we stop (S1) and
      // restart (R1) again (though unlikely, this could happen if the user
      // opens another `about:torconnect` tab after S1 and presses the restart
      // button). Rather than create a new timeout, which would delay the
      // restart display for even longer, we maintain the current one.
    }

    this.updateProviderStoppedContent();
  }

  /**
   * Whether we are currently *showing* the tor provider as restarting in the
   * UI.
   *
   * @type {boolean}
   */
  restartingProvider = false;

  /**
   * Called to update the page content for the `ProviderStopped` stage.
   */
  updateProviderStoppedContent() {
    const {
      isRestarting,
      forceShowRestarting,
      canShowFailedAlert,
      maybeConfigIssue,
    } = this._providerStoppedData;

    // Hide any of the debugging information related to configuration issues if
    // we do not suspect that this could be the cause.
    document.body.classList.toggle(
      "hide-provider-config-debugging",
      !maybeConfigIssue
    );

    const showRestarting = isRestarting || forceShowRestarting;
    this.elements.restartProviderFailed.hidden =
      showRestarting || !canShowFailedAlert;

    this.restartingProvider = showRestarting;
    // The button is functionally disabled, but can still keep focus.
    // TODO: tor-browser#45050. Disabled attribute is waiting on bugzilla bug
    // 1927727.
    // this.elements.restartProviderButton.disabled = showRestarting;
    this.elements.restartProviderButton.iconSrc = showRestarting
      ? "chrome://global/skin/icons/loading.svg"
      : null;
    document.l10n.setAttributes(
      this.elements.restartProviderButton,
      showRestarting
        ? "tor-connect-tor-not-working-restarting-button"
        : "tor-connect-tor-not-working-restart-button"
    );
  }

  updateBootstrappingStatus(data) {
    this.elements.progressMeter.style.setProperty(
      "--progress-percent",
@@ -816,6 +973,14 @@ class AboutTorConnect {
      RPMSendAsyncMessage("torconnect:restart");
    });

    this.elements.restartProviderButton.addEventListener("click", () => {
      if (this.restartingProvider) {
        // Ignore clicks whilst the button is shown as "Restarting".
        return;
      }
      RPMSendAsyncMessage("torconnect:restart-provider");
    });

    this.elements.configureButton.textContent =
      TorStrings.torConnect.torConfigure;
    this.elements.configureButton.addEventListener("click", () => {
@@ -896,6 +1061,9 @@ class AboutTorConnect {
    RPMAddMessageListener("torconnect:stage-change", ({ data }) => {
      this.updateStage(data);
    });
    RPMAddMessageListener("torconnect:provider-status-change", ({ data }) => {
      this.updateProviderStatus(data, false);
    });
    RPMAddMessageListener("torconnect:bootstrap-progress", ({ data }) => {
      this.updateBootstrappingStatus(data);
    });
+3 −0
Original line number Diff line number Diff line
@@ -288,6 +288,7 @@ export let RemotePageAccessManager = {
    "about:torconnect": {
      RPMAddMessageListener: [
        "torconnect:stage-change",
        "torconnect:provider-status-change",
        "torconnect:bootstrap-progress",
        "torconnect:quickstart-change",
        "torconnect:region-names-change",
@@ -299,10 +300,12 @@ export let RemotePageAccessManager = {
        "torconnect:set-quickstart",
        "torconnect:view-tor-logs",
        "torconnect:restart",
        "torconnect:restart-provider",
        "torconnect:start-again",
        "torconnect:choose-region",
      ],
      RPMSendQuery: ["torconnect:get-init-args", "torconnect:get-regions"],
      RPMGetFormatURLPref: ["app.support.baseURL"],
    },
    "about:welcome": {
      RPMSendAsyncMessage: ["ActivityStream:ContentToMain"],