Commit 6a6cb5e8 authored by henry's avatar henry Committed by morgan
Browse files

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

TB 45236: Make the breadcrumbs focusable and activatable with a
keyboard.

We also update the breadcrumb states to be more consistent between
stages, and we no longer disable prior breadcrumbs during a bootstrap
since it is safe for the user to switch back to a prior stage.
parent 17053ff9
Loading
Loading
Loading
Loading
+31 −36
Original line number Diff line number Diff line
@@ -25,42 +25,59 @@ body {
#breadcrumbs {
  grid-area: breadcrumbs;
  display: flex;
  align-items: center;
  gap: var(--space-large);
  margin: 0 0 var(--space-xlarge) 0;
}

#breadcrumbs.hidden {
  &.hide-breadcrumbs {
    visibility: hidden;
  }

  & :is(ol, li:not([hidden])) {
    display: contents;
  }
}

.breadcrumb-item,
.breadcrumb-separator {
  display: flex;
  margin: 0;
  margin-inline-start: var(--space-large);
  flex: 0 0 auto;
  padding: var(--space-small);
}

.breadcrumb-item {
  display: flex;
  gap: var(--space-xsmall);
  align-items: center;
  cursor: pointer;
  color: var(--text-color);
  border-radius: var(--border-radius-small);

  & > * {
    flex: 0 0 auto;
  }

.breadcrumb-item:hover {
  &:not(.breadcrumb-disabled):hover {
    color: var(--color-accent-primary);
    background-color: var(--button-background-color-hover);
  }

.breadcrumb-item:active {
  &:not(.breadcrumb-disabled):active {
    color: var(--color-accent-primary-active);
    background-color: var(--button-background-color-active);
  }

  &.breadcrumb-active {
    color: var(--color-accent-primary);
  }

  &.breadcrumb-disabled {
    color: var(--text-color);
    opacity: 0.4;
    cursor: unset;
  }
}

.breadcrumb-separator {
  width: var(--icon-size);
  list-style-image: url("chrome://browser/content/torconnect/arrow-right.svg");
  content: url("chrome://browser/content/torconnect/arrow-right.svg");
}

.breadcrumb-separator:dir(rtl) {
@@ -68,49 +85,27 @@ body {
}

.breadcrumb-icon {
  display: inline list-item;
  width: var(--icon-size);
  height: var(--icon-size);
  list-style-position: inside;
  -moz-context-properties: fill, stroke;
  fill: currentColor;
  stroke: currentColor;
}

.breadcrumb-item.active {
  color: var(--color-accent-primary);
}

.breadcrumb-item.disabled,
.breadcrumb-item.disabled:hover,
.breadcrumb-item.disabled:active {
  color: var(--text-color);
  opacity: 0.4;
  cursor: default;
}

.breadcrumb-item.hidden,
.breadcrumb-separator.hidden {
  display: none;
}

#connect-to-tor {
  margin-inline-start: 0;
}

#connect-to-tor-icon {
  list-style-image: url("chrome://browser/content/torconnect/tor-connect.svg");
  content: url("chrome://browser/content/torconnect/tor-connect.svg");
}

#connection-assist-icon {
  list-style-image: url("chrome://browser/content/torconnect/tor-connect-broken.svg");
}

#try-bridge {
  cursor: default;
  content: url("chrome://browser/content/torconnect/tor-connect-broken.svg");
}

#try-bridge-icon {
  list-style-image: url("chrome://browser/content/torconnect/bridge.svg");
  content: url("chrome://browser/content/torconnect/bridge.svg");
}

#locationDropdownLabel.error {
+17 −15
Original line number Diff line number Diff line
@@ -38,28 +38,30 @@
      <div id="progressBackground"></div>
      <div id="progressBarBackground"></div>
    </div>
    <div id="breadcrumbs" class="hidden">
      <span id="connect-to-tor" class="breadcrumb-item">
    <nav id="breadcrumbs">
      <ol>
        <li id="connect-to-tor">
          <div class="breadcrumb-item" role="link" tabindex="0">
            <span id="connect-to-tor-icon" class="breadcrumb-icon"></span>
            <span class="breadcrumb-label"></span>
      </span>
      <span
        id="connection-assist-separator"
        class="breadcrumb-separator breadcrumb-icon"
      ></span>
      <span id="connection-assist" class="breadcrumb-item">
          </div>
        </li>
        <li id="connection-assist">
          <div class="breadcrumb-separator breadcrumb-icon"></div>
          <div class="breadcrumb-item" role="link" tabindex="0">
            <span id="connection-assist-icon" class="breadcrumb-icon"></span>
            <span class="breadcrumb-label"></span>
      </span>
      <span
        id="try-bridge-separator"
        class="breadcrumb-separator breadcrumb-icon"
      ></span>
      <span id="try-bridge" class="breadcrumb-item">
          </div>
        </li>
        <li id="try-bridge">
          <div class="breadcrumb-separator breadcrumb-icon"></div>
          <div class="breadcrumb-item" role="link" tabindex="0">
            <span id="try-bridge-icon" class="breadcrumb-icon"></span>
            <span class="breadcrumb-label"></span>
      </span>
          </div>
        </li>
      </ol>
    </nav>
    <div class="torconnect-stage-content" data-stage-name="ProviderStopped">
      <img class="torconnect-icon assist-icon" alt="" />
      <h1
+81 −53
Original line number Diff line number Diff line
@@ -42,12 +42,10 @@ class AboutTorConnect {
        label: "#connect-to-tor .breadcrumb-label",
      },
      connectionAssist: {
        separator: "#connection-assist-separator",
        link: "#connection-assist",
        label: "#connection-assist .breadcrumb-label",
      },
      tryBridge: {
        separator: "#try-bridge-separator",
        link: "#try-bridge",
        label: "#try-bridge .breadcrumb-label",
      },
@@ -90,18 +88,12 @@ class AboutTorConnect {
    connectToTorLabel: document.querySelector(
      this.selectors.breadcrumbs.connectToTor.label
    ),
    connectionAssistSeparator: document.querySelector(
      this.selectors.breadcrumbs.connectionAssist.separator
    ),
    connectionAssistLink: document.querySelector(
      this.selectors.breadcrumbs.connectionAssist.link
    ),
    connectionAssistLabel: document.querySelector(
      this.selectors.breadcrumbs.connectionAssist.label
    ),
    tryBridgeSeparator: document.querySelector(
      this.selectors.breadcrumbs.tryBridge.separator
    ),
    tryBridgeLink: document.querySelector(
      this.selectors.breadcrumbs.tryBridge.link
    ),
@@ -239,33 +231,49 @@ class AboutTorConnect {
  }

  setBreadcrumbsStatus(connectToTor, connectionAssist, tryBridge) {
    this.elements.breadcrumbContainer.classList.remove("hidden");
    const elems = [
      [this.elements.connectToTorLink, connectToTor, null],
      [
        this.elements.connectionAssistLink,
        connectionAssist,
        this.elements.connectionAssistSeparator,
      ],
      [
        this.elements.tryBridgeLink,
        tryBridge,
        this.elements.tryBridgeSeparator,
      ],
    ];
    elems.forEach(([elem, status, separator]) => {
      elem.classList.remove(BreadcrumbStatus.Hidden);
      elem.classList.remove(BreadcrumbStatus.Disabled);
      elem.classList.remove(BreadcrumbStatus.Active);
      if (status !== "") {
        elem.classList.add(status);
      }
      separator?.classList.toggle("hidden", status === BreadcrumbStatus.Hidden);
    });
    this.elements.breadcrumbContainer.classList.remove("hide-breadcrumbs");
    for (const { listEl, status } of [
      { listEl: this.elements.connectToTorLink, status: connectToTor },
      { listEl: this.elements.connectionAssistLink, status: connectionAssist },
      { listEl: this.elements.tryBridgeLink, status: tryBridge },
    ]) {
      let hidden = false;
      let disabled = false;
      let active = false;
      switch (status) {
        case BreadcrumbStatus.Hidden:
          hidden = true;
          break;
        case BreadcrumbStatus.Disabled:
          disabled = true;
          break;
        case BreadcrumbStatus.Active:
          active = true;
          break;
      }
      // NOTE: Hiding or disabling elements can cause them to loose focus.
      // However, with each stage change the focus should already be moved.
      listEl.hidden = hidden;
      const linkEl = listEl.querySelector(".breadcrumb-item");
      linkEl.classList.toggle("breadcrumb-disabled", disabled);
      linkEl.classList.toggle("breadcrumb-active", active);
      if (disabled) {
        linkEl.removeAttribute("tabindex");
        linkEl.setAttribute("aria-disabled", "true");
      } else {
        linkEl.setAttribute("tabindex", "0");
        linkEl.removeAttribute("aria-disabled", "true");
      }
      if (active) {
        linkEl.setAttribute("aria-current", "step");
      } else {
        linkEl.removeAttribute("aria-current");
      }
    }
  }

  hideBreadcrumbs() {
    this.elements.breadcrumbContainer.classList.add("hidden");
    this.elements.breadcrumbContainer.classList.add("hide-breadcrumbs");
  }

  getLocalizedStatus(status) {
@@ -432,7 +440,11 @@ class AboutTorConnect {
        break;
      case "Bootstrapping":
        showProgress = true;
        this.showBootstrapping(stage.bootstrapTrigger, stage.tryAgain);
        this.showBootstrapping(
          stage.bootstrapTrigger,
          stage.tryAgain,
          stage.potentiallyBlocked
        );
        // Always focus the cancel button.
        moveFocus = this.elements.cancelButton;
        break;
@@ -676,16 +688,18 @@ class AboutTorConnect {
        BreadcrumbStatus.Default,
        BreadcrumbStatus.Disabled
      );
    } else {
      this.hideBreadcrumbs();
    }
  }

  showBootstrapping(trigger, tryAgain) {
  showBootstrapping(trigger, tryAgain, potentiallyBlocked) {
    let title = "";
    let description = "";
    const breadcrumbs = [
      BreadcrumbStatus.Disabled,
      BreadcrumbStatus.Disabled,
      BreadcrumbStatus.Disabled,
      BreadcrumbStatus.Default,
      potentiallyBlocked ? BreadcrumbStatus.Default : BreadcrumbStatus.Hidden,
      potentiallyBlocked ? BreadcrumbStatus.Disabled : BreadcrumbStatus.Hidden,
    ];
    switch (trigger) {
      case "Start":
@@ -718,7 +732,7 @@ class AboutTorConnect {
    this.setTitle(title, "");
    this.showConfigureConnectionLink(description);
    this.elements.progressDescription.textContent = "";
    if (tryAgain) {
    if (tryAgain || potentiallyBlocked) {
      this.setBreadcrumbsStatus(...breadcrumbs);
    } else {
      this.hideBreadcrumbs();
@@ -780,8 +794,8 @@ class AboutTorConnect {
      this.getMaybeLocalizedError(error);
    this.setBreadcrumbsStatus(
      BreadcrumbStatus.Default,
      BreadcrumbStatus.Default,
      BreadcrumbStatus.Active
      BreadcrumbStatus.Active,
      BreadcrumbStatus.Disabled
    );
    this.showLocationForm(false, TorStrings.torConnect.tryAgain);
  }
@@ -929,24 +943,38 @@ class AboutTorConnect {
  initElements(direction) {
    document.documentElement.setAttribute("dir", direction);

    this.elements.connectToTorLink.addEventListener("click", () => {
    for (const [linkEl, command] of [
      [
        this.elements.connectToTorLink,
        () => {
          RPMSendAsyncMessage("torconnect:start-again");
        },
      ],
      [
        this.elements.connectionAssistLink,
        () => {
          RPMSendAsyncMessage("torconnect:choose-region");
        },
      ],
      // Final link does nothing.
    ]) {
      // NOTE: These link should never be both visible and disabled.
      // NOTE: We allow re-selecting this link when it is already the
      // "aria-current" item, to allow the user to reset to the beginning of
      // Connection Assist.
      linkEl.addEventListener("click", () => {
        command();
      });
    this.elements.connectToTorLabel.textContent =
      TorStrings.torConnect.torConnect;
    this.elements.connectionAssistLink.addEventListener("click", () => {
      if (
        this.elements.connectionAssistLink.classList.contains(
          BreadcrumbStatus.Active
        ) ||
        this.elements.connectionAssistLink.classList.contains(
          BreadcrumbStatus.Disabled
        )
      ) {
      linkEl.addEventListener("keydown", event => {
        if (event.key !== "Enter") {
          return;
        }
      RPMSendAsyncMessage("torconnect:choose-region");
        command();
      });
    }

    this.elements.connectToTorLabel.textContent =
      TorStrings.torConnect.torConnect;
    this.elements.connectionAssistLabel.textContent =
      TorStrings.torConnect.breadcrumbAssist;
    this.elements.tryBridgeLabel.textContent =