[tor-commits] [Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.5-1] 2 commits: fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in...

richard (@richard) git at gitlab.torproject.org
Wed Jan 31 10:51:12 UTC 2024



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


Commits:
d66eecaa by Henry Wilkes at 2024-01-31T09:28:02+00:00
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection

Bug 42385: Add lox invite dialog.

- - - - -
48110609 by Henry Wilkes at 2024-01-31T09:28:03+00:00
fixup! Tor Browser strings

Bug 42385: Add lox invite dialog strings.

- - - - -


6 changed files:

- browser/components/torpreferences/content/connectionPane.js
- + browser/components/torpreferences/content/loxInviteDialog.js
- + browser/components/torpreferences/content/loxInviteDialog.xhtml
- browser/components/torpreferences/content/torPreferences.css
- browser/components/torpreferences/jar.mn
- browser/locales/en-US/browser/tor-browser.ftl


Changes:

=====================================
browser/components/torpreferences/content/connectionPane.js
=====================================
@@ -1321,7 +1321,17 @@ const gLoxStatus = {
     );
 
     this._invitesButton.addEventListener("click", () => {
-      // TODO: Show invites.
+      gSubDialog.open(
+        "chrome://browser/content/torpreferences/loxInviteDialog.xhtml",
+        {
+          features: "resizable=yes",
+          closedCallback: () => {
+            // TODO: Listen for events from Lox, rather than call _updateInvites
+            // directly.
+            this._updateInvites();
+          },
+        }
+      );
     });
     this._unlockAlertButton.addEventListener("click", () => {
       // TODO: Have a way to ensure that the cleared event data matches the


=====================================
browser/components/torpreferences/content/loxInviteDialog.js
=====================================
@@ -0,0 +1,347 @@
+"use strict";
+
+const { TorSettings, TorSettingsTopics, TorBridgeSource } =
+  ChromeUtils.importESModule("resource://gre/modules/TorSettings.sys.mjs");
+
+const { Lox, LoxErrors } = ChromeUtils.importESModule(
+  "resource://gre/modules/Lox.sys.mjs"
+);
+
+/**
+ * Fake Lox module
+
+const LoxErrors = {
+  LoxServerUnreachable: "LoxServerUnreachable",
+  Other: "Other",
+};
+
+const Lox = {
+  remainingInvites: 5,
+  getRemainingInviteCount() {
+    return this.remainingInvites;
+  },
+  invites: [
+    '{"invite": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22]}',
+    '{"invite": [9,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22]}',
+  ],
+  getInvites() {
+    return this.invites;
+  },
+  failError: null,
+  generateInvite() {
+    return new Promise((res, rej) => {
+      setTimeout(() => {
+        if (this.failError) {
+          rej({ type: this.failError });
+          return;
+        }
+        if (!this.remainingInvites) {
+          rej({ type: LoxErrors.Other });
+          return;
+        }
+        const invite = JSON.stringify({
+          invite: Array.from({ length: 100 }, () =>
+            Math.floor(Math.random() * 265)
+          ),
+        });
+        this.invites.push(invite);
+        this.remainingInvites--;
+        res(invite);
+      }, 4000);
+    });
+  },
+};
+*/
+
+const gLoxInvites = {
+  /**
+   * Initialize the dialog.
+   */
+  init() {
+    this._dialog = document.getElementById("lox-invite-dialog");
+    this._remainingInvitesEl = document.getElementById(
+      "lox-invite-dialog-remaining"
+    );
+    this._generateButton = document.getElementById(
+      "lox-invite-dialog-generate-button"
+    );
+    this._connectingEl = document.getElementById(
+      "lox-invite-dialog-connecting"
+    );
+    this._errorEl = document.getElementById("lox-invite-dialog-error-message");
+    this._inviteListEl = document.getElementById("lox-invite-dialog-list");
+
+    this._generateButton.addEventListener("click", () => {
+      this._generateNewInvite();
+    });
+
+    const menu = document.getElementById("lox-invite-dialog-item-menu");
+    this._inviteListEl.addEventListener("contextmenu", event => {
+      if (!this._inviteListEl.selectedItem) {
+        return;
+      }
+      menu.openPopupAtScreen(event.screenX, event.screenY, true);
+    });
+    menu.addEventListener("popuphidden", () => {
+      menu.setAttribute("aria-hidden", "true");
+    });
+    menu.addEventListener("popupshowing", () => {
+      menu.removeAttribute("aria-hidden");
+    });
+    document
+      .getElementById("lox-invite-dialog-copy-menu-item")
+      .addEventListener("command", () => {
+        const selected = this._inviteListEl.selectedItem;
+        if (!selected) {
+          return;
+        }
+        const clipboard = Cc[
+          "@mozilla.org/widget/clipboardhelper;1"
+        ].getService(Ci.nsIClipboardHelper);
+        clipboard.copyString(selected.textContent);
+      });
+
+    // NOTE: TorSettings should already be initialized when this dialog is
+    // opened.
+    Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged);
+    // TODO: Listen for new invites from Lox, when supported.
+
+    // Set initial _loxId value. Can close this dialog.
+    this._updateLoxId();
+
+    this._updateRemainingInvites();
+    this._updateExistingInvites();
+  },
+
+  /**
+   * Un-initialize the dialog.
+   */
+  uninit() {
+    Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
+  },
+
+  observe(subject, topic, data) {
+    switch (topic) {
+      case TorSettingsTopics.SettingsChanged:
+        const { changes } = subject.wrappedJSObject;
+        if (
+          changes.includes("bridges.source") ||
+          changes.includes("bridges.lox_id")
+        ) {
+          this._updateLoxId();
+        }
+        break;
+    }
+  },
+
+  /**
+   * The loxId this dialog is shown for. null if uninitailized.
+   *
+   * @type {string?}
+   */
+  _loxId: null,
+  /**
+   * Update the _loxId value. Will close the dialog if it changes after
+   * initialization.
+   */
+  _updateLoxId() {
+    const loxId =
+      TorSettings.bridges.source === TorBridgeSource.Lox
+        ? TorSettings.bridges.lox_id
+        : "";
+    if (!loxId || (this._loxId !== null && loxId !== this._loxId)) {
+      // No lox id, or it changed. Close this dialog.
+      this._dialog.cancelDialog();
+    }
+    this._loxId = loxId;
+  },
+
+  /**
+   * The invites that are already shown.
+   *
+   * @type {Set<string>}
+   */
+  _shownInvites: new Set(),
+
+  /**
+   * Add a new invite at the start of the list.
+   *
+   * @param {string} invite - The invite to add.
+   */
+  _addInvite(invite) {
+    if (this._shownInvites.has(invite)) {
+      return;
+    }
+    const newInvite = document.createXULElement("richlistitem");
+    newInvite.classList.add("lox-invite-dialog-list-item");
+    newInvite.textContent = invite;
+
+    this._inviteListEl.prepend(newInvite);
+    this._shownInvites.add(invite);
+  },
+
+  /**
+   * Update the display of the existing invites.
+   */
+  _updateExistingInvites() {
+    // Add new invites.
+
+    // NOTE: we only expect invites to be appended, so we won't re-order any.
+    // NOTE: invites are ordered with the oldest first.
+    for (const invite of Lox.getInvites()) {
+      this._addInvite(invite);
+    }
+  },
+
+  /**
+   * The shown number or remaining invites we have.
+   *
+   * @type {integer}
+   */
+  _remainingInvites: 0,
+
+  /**
+   * Update the display of the remaining invites.
+   */
+  _updateRemainingInvites() {
+    this._remainingInvites = Lox.getRemainingInviteCount();
+
+    document.l10n.setAttributes(
+      this._remainingInvitesEl,
+      "tor-bridges-lox-remaining-invites",
+      { numInvites: this._remainingInvites }
+    );
+    this._updateGenerateButtonState();
+  },
+
+  /**
+   * Whether we are currently generating an invite.
+   *
+   * @type {boolean}
+   */
+  _generating: false,
+  /**
+   * Set whether we are generating an invite.
+   *
+   * @param {boolean} isGenerating - Whether we are generating.
+   */
+  _setGenerating(isGenerating) {
+    this._generating = isGenerating;
+    this._updateGenerateButtonState();
+    this._connectingEl.classList.toggle("show-connecting", isGenerating);
+  },
+
+  /**
+   * Update the state of the generate button.
+   */
+  _updateGenerateButtonState() {
+    this._generateButton.disabled = this._generating || !this._remainingInvites;
+  },
+
+  /**
+   * Start generating a new invite.
+   */
+  _generateNewInvite() {
+    if (this._generating) {
+      console.error("Already generating an invite");
+      return;
+    }
+    this._setGenerating(true);
+    // Clear the previous error.
+    this._updateGenerateError(null);
+    // Move focus from the button to the connecting element, since button is
+    // now disabled.
+    this._connectingEl.focus();
+
+    let lostFocus = false;
+    Lox.generateInvite()
+      .finally(() => {
+        // Fetch whether the connecting label still has focus before we hide it.
+        lostFocus = this._connectingEl.contains(document.activeElement);
+        this._setGenerating(false);
+      })
+      .then(
+        invite => {
+          this._addInvite(invite);
+
+          if (!this._inviteListEl.contains(document.activeElement)) {
+            // Does not have focus, change the selected item to be the new
+            // invite (at index 0).
+            this._inviteListEl.selectedIndex = 0;
+          }
+
+          if (lostFocus) {
+            // Move focus to the new invite before we hide the "Connecting"
+            // message.
+            this._inviteListEl.focus();
+          }
+
+          // TODO: When Lox sends out notifications, let the observer handle the
+          // change rather than calling _updateRemainingInvites directly.
+          this._updateRemainingInvites();
+        },
+        loxError => {
+          console.error("Failed to generate an invite", loxError);
+          switch (loxError.type) {
+            case LoxErrors.LoxServerUnreachable:
+              this._updateGenerateError("no-server");
+              break;
+            default:
+              this._updateGenerateError("generic");
+              break;
+          }
+
+          if (lostFocus) {
+            // Move focus back to the button before we hide the "Connecting"
+            // message.
+            this._generateButton.focus();
+          }
+        }
+      );
+  },
+
+  /**
+   * Update the shown generation error.
+   *
+   * @param {string?} type - The error type, or null if no error should be
+   *   shown.
+   */
+  _updateGenerateError(type) {
+    // First clear the existing error.
+    this._errorEl.removeAttribute("data-l10n-id");
+    this._errorEl.textContent = "";
+    this._errorEl.classList.toggle("show-error", !!type);
+
+    if (!type) {
+      return;
+    }
+
+    let errorId;
+    switch (type) {
+      case "no-server":
+        errorId = "lox-invite-dialog-no-server-error";
+        break;
+      case "generic":
+        // Generic error.
+        errorId = "lox-invite-dialog-generic-invite-error";
+        break;
+    }
+
+    document.l10n.setAttributes(this._errorEl, errorId);
+  },
+};
+
+window.addEventListener(
+  "DOMContentLoaded",
+  () => {
+    gLoxInvites.init();
+    window.addEventListener(
+      "unload",
+      () => {
+        gLoxInvites.uninit();
+      },
+      { once: true }
+    );
+  },
+  { once: true }
+);


=====================================
browser/components/torpreferences/content/loxInviteDialog.xhtml
=====================================
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
+
+<window
+  type="child"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  xmlns:html="http://www.w3.org/1999/xhtml"
+  data-l10n-id="lox-invite-dialog-title"
+>
+  <!-- Context menu, aria-hidden whilst not shown so it does not appear in the
+     - document content. -->
+  <menupopup id="lox-invite-dialog-item-menu" aria-hidden="true">
+    <menuitem
+      id="lox-invite-dialog-copy-menu-item"
+      data-l10n-id="lox-invite-dialog-menu-item-copy-invite"
+    />
+  </menupopup>
+  <dialog id="lox-invite-dialog" buttons="accept">
+    <linkset>
+      <html:link rel="localization" href="browser/tor-browser.ftl" />
+    </linkset>
+
+    <script src="chrome://browser/content/torpreferences/loxInviteDialog.js" />
+
+    <description data-l10n-id="lox-invite-dialog-description"></description>
+    <html:div id="lox-invite-dialog-generate-area">
+      <html:span id="lox-invite-dialog-remaining"></html:span>
+      <html:button
+        id="lox-invite-dialog-generate-button"
+        data-l10n-id="lox-invite-dialog-request-button"
+      ></html:button>
+      <html:div id="lox-invite-dialog-message-area">
+        <html:span
+          id="lox-invite-dialog-error-message"
+          role="alert"
+        ></html:span>
+        <html:span
+          id="lox-invite-dialog-connecting"
+          role="alert"
+          tabindex="0"
+          data-l10n-id="lox-invite-dialog-connecting"
+        ></html:span>
+      </html:div>
+    </html:div>
+    <html:div
+      id="lox-invite-dialog-list-label"
+      data-l10n-id="lox-invite-dialog-invites-label"
+    ></html:div>
+    <richlistbox
+      id="lox-invite-dialog-list"
+      aria-labelledby="lox-invite-dialog-list-label"
+    ></richlistbox>
+  </dialog>
+</window>


=====================================
browser/components/torpreferences/content/torPreferences.css
=====================================
@@ -820,6 +820,75 @@ dialog#torPreferences-requestBridge-dialog > hbox {
   background: var(--qr-one);
 }
 
+/* Lox invite dialog */
+
+#lox-invite-dialog-generate-area {
+  flex: 0 0 auto;
+  display: grid;
+  grid-template:
+    ". remaining button" min-content
+    "message message message" auto
+    / 1fr max-content max-content;
+  gap: 8px;
+  margin-block: 16px 8px;
+  align-items: center;
+}
+
+#lox-invite-dialog-remaining {
+  grid-area: remaining;
+}
+
+#lox-invite-dialog-generate-button {
+  grid-area: button;
+}
+
+#lox-invite-dialog-message-area {
+  grid-area: message;
+  justify-self: end;
+}
+
+#lox-invite-dialog-message-area::after {
+  /* Zero width space, to ensure we are always one line high. */
+  content: "\200B";
+}
+
+#lox-invite-dialog-error-message {
+  color: var(--in-content-error-text-color);
+}
+
+#lox-invite-dialog-error-message:not(.show-error) {
+  display: none;
+}
+
+#lox-invite-dialog-connecting {
+  color: var(--text-color-deemphasized);
+  /* TODO: Add spinner ::before */
+}
+
+#lox-invite-dialog-connecting:not(.show-connecting) {
+  display: none;
+}
+
+#lox-invite-dialog-list-label {
+  font-weight: 700;
+}
+
+#lox-invite-dialog-list {
+  flex: 1 1 auto;
+  /* basis height */
+  height: 10em;
+  margin-block: 8px;
+}
+
+.lox-invite-dialog-list-item {
+  white-space: nowrap;
+  overflow-x: hidden;
+  /* FIXME: ellipsis does not show. */
+  text-overflow: ellipsis;
+  padding-block: 6px;
+  padding-inline: 8px;
+}
+
 /* Builtin bridge dialog */
 #torPreferences-builtinBridge-header {
   margin: 8px 0 10px 0;


=====================================
browser/components/torpreferences/jar.mn
=====================================
@@ -9,6 +9,8 @@ browser.jar:
     content/browser/torpreferences/lox-success.svg                   (content/lox-success.svg)
     content/browser/torpreferences/lox-complete-ring.svg             (content/lox-complete-ring.svg)
     content/browser/torpreferences/lox-progress-ring.svg             (content/lox-progress-ring.svg)
+    content/browser/torpreferences/loxInviteDialog.xhtml             (content/loxInviteDialog.xhtml)
+    content/browser/torpreferences/loxInviteDialog.js                (content/loxInviteDialog.js)
     content/browser/torpreferences/bridgeQrDialog.xhtml              (content/bridgeQrDialog.xhtml)
     content/browser/torpreferences/bridgeQrDialog.js                 (content/bridgeQrDialog.js)
     content/browser/torpreferences/builtinBridgeDialog.xhtml         (content/builtinBridgeDialog.xhtml)


=====================================
browser/locales/en-US/browser/tor-browser.ftl
=====================================
@@ -296,3 +296,17 @@ user-provide-bridge-dialog-result-invite = The following bridges were shared wit
 user-provide-bridge-dialog-result-addresses = The following bridges were entered by you.
 user-provide-bridge-dialog-next-button =
     .label = Next
+
+## Bridge pass invite dialog. Temporary.
+
+lox-invite-dialog-title =
+    .title = Bridge pass invites
+lox-invite-dialog-description = You can ask the bridge bot to create a new invite, which you can share with a trusted contact to give them their own bridge pass. Each invite can only be redeemed once, but you will unlock access to more invites over time.
+lox-invite-dialog-request-button = Request new invite
+lox-invite-dialog-connecting = Connecting to bridge pass server…
+lox-invite-dialog-no-server-error = Unable to connect to bridge pass server.
+lox-invite-dialog-generic-invite-error = Failed to create a new invite.
+lox-invite-dialog-invites-label = Created invites:
+lox-invite-dialog-menu-item-copy-invite =
+    .label = Copy invite
+    .accesskey = C



View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/8069e4ee62453a13f3318adcca8d6125a0682b52...48110609a3914bd163e99a50012b3914daefbb96

-- 
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/8069e4ee62453a13f3318adcca8d6125a0682b52...48110609a3914bd163e99a50012b3914daefbb96
You're receiving this email because of your account on gitlab.torproject.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.torproject.org/pipermail/tor-commits/attachments/20240131/62849fb7/attachment-0001.htm>


More information about the tor-commits mailing list