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
-
48110609
by Henry Wilkes at 2024-01-31T09:28:03+00:00
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:
| ... | ... | @@ -1321,7 +1321,17 @@ const gLoxStatus = { |
| 1321 | 1321 | );
|
| 1322 | 1322 | |
| 1323 | 1323 | this._invitesButton.addEventListener("click", () => {
|
| 1324 | - // TODO: Show invites.
|
|
| 1324 | + gSubDialog.open(
|
|
| 1325 | + "chrome://browser/content/torpreferences/loxInviteDialog.xhtml",
|
|
| 1326 | + {
|
|
| 1327 | + features: "resizable=yes",
|
|
| 1328 | + closedCallback: () => {
|
|
| 1329 | + // TODO: Listen for events from Lox, rather than call _updateInvites
|
|
| 1330 | + // directly.
|
|
| 1331 | + this._updateInvites();
|
|
| 1332 | + },
|
|
| 1333 | + }
|
|
| 1334 | + );
|
|
| 1325 | 1335 | });
|
| 1326 | 1336 | this._unlockAlertButton.addEventListener("click", () => {
|
| 1327 | 1337 | // TODO: Have a way to ensure that the cleared event data matches the
|
| 1 | +"use strict";
|
|
| 2 | + |
|
| 3 | +const { TorSettings, TorSettingsTopics, TorBridgeSource } =
|
|
| 4 | + ChromeUtils.importESModule("resource://gre/modules/TorSettings.sys.mjs");
|
|
| 5 | + |
|
| 6 | +const { Lox, LoxErrors } = ChromeUtils.importESModule(
|
|
| 7 | + "resource://gre/modules/Lox.sys.mjs"
|
|
| 8 | +);
|
|
| 9 | + |
|
| 10 | +/**
|
|
| 11 | + * Fake Lox module
|
|
| 12 | + |
|
| 13 | +const LoxErrors = {
|
|
| 14 | + LoxServerUnreachable: "LoxServerUnreachable",
|
|
| 15 | + Other: "Other",
|
|
| 16 | +};
|
|
| 17 | + |
|
| 18 | +const Lox = {
|
|
| 19 | + remainingInvites: 5,
|
|
| 20 | + getRemainingInviteCount() {
|
|
| 21 | + return this.remainingInvites;
|
|
| 22 | + },
|
|
| 23 | + invites: [
|
|
| 24 | + '{"invite": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22]}',
|
|
| 25 | + '{"invite": [9,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22]}',
|
|
| 26 | + ],
|
|
| 27 | + getInvites() {
|
|
| 28 | + return this.invites;
|
|
| 29 | + },
|
|
| 30 | + failError: null,
|
|
| 31 | + generateInvite() {
|
|
| 32 | + return new Promise((res, rej) => {
|
|
| 33 | + setTimeout(() => {
|
|
| 34 | + if (this.failError) {
|
|
| 35 | + rej({ type: this.failError });
|
|
| 36 | + return;
|
|
| 37 | + }
|
|
| 38 | + if (!this.remainingInvites) {
|
|
| 39 | + rej({ type: LoxErrors.Other });
|
|
| 40 | + return;
|
|
| 41 | + }
|
|
| 42 | + const invite = JSON.stringify({
|
|
| 43 | + invite: Array.from({ length: 100 }, () =>
|
|
| 44 | + Math.floor(Math.random() * 265)
|
|
| 45 | + ),
|
|
| 46 | + });
|
|
| 47 | + this.invites.push(invite);
|
|
| 48 | + this.remainingInvites--;
|
|
| 49 | + res(invite);
|
|
| 50 | + }, 4000);
|
|
| 51 | + });
|
|
| 52 | + },
|
|
| 53 | +};
|
|
| 54 | +*/
|
|
| 55 | + |
|
| 56 | +const gLoxInvites = {
|
|
| 57 | + /**
|
|
| 58 | + * Initialize the dialog.
|
|
| 59 | + */
|
|
| 60 | + init() {
|
|
| 61 | + this._dialog = document.getElementById("lox-invite-dialog");
|
|
| 62 | + this._remainingInvitesEl = document.getElementById(
|
|
| 63 | + "lox-invite-dialog-remaining"
|
|
| 64 | + );
|
|
| 65 | + this._generateButton = document.getElementById(
|
|
| 66 | + "lox-invite-dialog-generate-button"
|
|
| 67 | + );
|
|
| 68 | + this._connectingEl = document.getElementById(
|
|
| 69 | + "lox-invite-dialog-connecting"
|
|
| 70 | + );
|
|
| 71 | + this._errorEl = document.getElementById("lox-invite-dialog-error-message");
|
|
| 72 | + this._inviteListEl = document.getElementById("lox-invite-dialog-list");
|
|
| 73 | + |
|
| 74 | + this._generateButton.addEventListener("click", () => {
|
|
| 75 | + this._generateNewInvite();
|
|
| 76 | + });
|
|
| 77 | + |
|
| 78 | + const menu = document.getElementById("lox-invite-dialog-item-menu");
|
|
| 79 | + this._inviteListEl.addEventListener("contextmenu", event => {
|
|
| 80 | + if (!this._inviteListEl.selectedItem) {
|
|
| 81 | + return;
|
|
| 82 | + }
|
|
| 83 | + menu.openPopupAtScreen(event.screenX, event.screenY, true);
|
|
| 84 | + });
|
|
| 85 | + menu.addEventListener("popuphidden", () => {
|
|
| 86 | + menu.setAttribute("aria-hidden", "true");
|
|
| 87 | + });
|
|
| 88 | + menu.addEventListener("popupshowing", () => {
|
|
| 89 | + menu.removeAttribute("aria-hidden");
|
|
| 90 | + });
|
|
| 91 | + document
|
|
| 92 | + .getElementById("lox-invite-dialog-copy-menu-item")
|
|
| 93 | + .addEventListener("command", () => {
|
|
| 94 | + const selected = this._inviteListEl.selectedItem;
|
|
| 95 | + if (!selected) {
|
|
| 96 | + return;
|
|
| 97 | + }
|
|
| 98 | + const clipboard = Cc[
|
|
| 99 | + "@mozilla.org/widget/clipboardhelper;1"
|
|
| 100 | + ].getService(Ci.nsIClipboardHelper);
|
|
| 101 | + clipboard.copyString(selected.textContent);
|
|
| 102 | + });
|
|
| 103 | + |
|
| 104 | + // NOTE: TorSettings should already be initialized when this dialog is
|
|
| 105 | + // opened.
|
|
| 106 | + Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged);
|
|
| 107 | + // TODO: Listen for new invites from Lox, when supported.
|
|
| 108 | + |
|
| 109 | + // Set initial _loxId value. Can close this dialog.
|
|
| 110 | + this._updateLoxId();
|
|
| 111 | + |
|
| 112 | + this._updateRemainingInvites();
|
|
| 113 | + this._updateExistingInvites();
|
|
| 114 | + },
|
|
| 115 | + |
|
| 116 | + /**
|
|
| 117 | + * Un-initialize the dialog.
|
|
| 118 | + */
|
|
| 119 | + uninit() {
|
|
| 120 | + Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
|
|
| 121 | + },
|
|
| 122 | + |
|
| 123 | + observe(subject, topic, data) {
|
|
| 124 | + switch (topic) {
|
|
| 125 | + case TorSettingsTopics.SettingsChanged:
|
|
| 126 | + const { changes } = subject.wrappedJSObject;
|
|
| 127 | + if (
|
|
| 128 | + changes.includes("bridges.source") ||
|
|
| 129 | + changes.includes("bridges.lox_id")
|
|
| 130 | + ) {
|
|
| 131 | + this._updateLoxId();
|
|
| 132 | + }
|
|
| 133 | + break;
|
|
| 134 | + }
|
|
| 135 | + },
|
|
| 136 | + |
|
| 137 | + /**
|
|
| 138 | + * The loxId this dialog is shown for. null if uninitailized.
|
|
| 139 | + *
|
|
| 140 | + * @type {string?}
|
|
| 141 | + */
|
|
| 142 | + _loxId: null,
|
|
| 143 | + /**
|
|
| 144 | + * Update the _loxId value. Will close the dialog if it changes after
|
|
| 145 | + * initialization.
|
|
| 146 | + */
|
|
| 147 | + _updateLoxId() {
|
|
| 148 | + const loxId =
|
|
| 149 | + TorSettings.bridges.source === TorBridgeSource.Lox
|
|
| 150 | + ? TorSettings.bridges.lox_id
|
|
| 151 | + : "";
|
|
| 152 | + if (!loxId || (this._loxId !== null && loxId !== this._loxId)) {
|
|
| 153 | + // No lox id, or it changed. Close this dialog.
|
|
| 154 | + this._dialog.cancelDialog();
|
|
| 155 | + }
|
|
| 156 | + this._loxId = loxId;
|
|
| 157 | + },
|
|
| 158 | + |
|
| 159 | + /**
|
|
| 160 | + * The invites that are already shown.
|
|
| 161 | + *
|
|
| 162 | + * @type {Set<string>}
|
|
| 163 | + */
|
|
| 164 | + _shownInvites: new Set(),
|
|
| 165 | + |
|
| 166 | + /**
|
|
| 167 | + * Add a new invite at the start of the list.
|
|
| 168 | + *
|
|
| 169 | + * @param {string} invite - The invite to add.
|
|
| 170 | + */
|
|
| 171 | + _addInvite(invite) {
|
|
| 172 | + if (this._shownInvites.has(invite)) {
|
|
| 173 | + return;
|
|
| 174 | + }
|
|
| 175 | + const newInvite = document.createXULElement("richlistitem");
|
|
| 176 | + newInvite.classList.add("lox-invite-dialog-list-item");
|
|
| 177 | + newInvite.textContent = invite;
|
|
| 178 | + |
|
| 179 | + this._inviteListEl.prepend(newInvite);
|
|
| 180 | + this._shownInvites.add(invite);
|
|
| 181 | + },
|
|
| 182 | + |
|
| 183 | + /**
|
|
| 184 | + * Update the display of the existing invites.
|
|
| 185 | + */
|
|
| 186 | + _updateExistingInvites() {
|
|
| 187 | + // Add new invites.
|
|
| 188 | + |
|
| 189 | + // NOTE: we only expect invites to be appended, so we won't re-order any.
|
|
| 190 | + // NOTE: invites are ordered with the oldest first.
|
|
| 191 | + for (const invite of Lox.getInvites()) {
|
|
| 192 | + this._addInvite(invite);
|
|
| 193 | + }
|
|
| 194 | + },
|
|
| 195 | + |
|
| 196 | + /**
|
|
| 197 | + * The shown number or remaining invites we have.
|
|
| 198 | + *
|
|
| 199 | + * @type {integer}
|
|
| 200 | + */
|
|
| 201 | + _remainingInvites: 0,
|
|
| 202 | + |
|
| 203 | + /**
|
|
| 204 | + * Update the display of the remaining invites.
|
|
| 205 | + */
|
|
| 206 | + _updateRemainingInvites() {
|
|
| 207 | + this._remainingInvites = Lox.getRemainingInviteCount();
|
|
| 208 | + |
|
| 209 | + document.l10n.setAttributes(
|
|
| 210 | + this._remainingInvitesEl,
|
|
| 211 | + "tor-bridges-lox-remaining-invites",
|
|
| 212 | + { numInvites: this._remainingInvites }
|
|
| 213 | + );
|
|
| 214 | + this._updateGenerateButtonState();
|
|
| 215 | + },
|
|
| 216 | + |
|
| 217 | + /**
|
|
| 218 | + * Whether we are currently generating an invite.
|
|
| 219 | + *
|
|
| 220 | + * @type {boolean}
|
|
| 221 | + */
|
|
| 222 | + _generating: false,
|
|
| 223 | + /**
|
|
| 224 | + * Set whether we are generating an invite.
|
|
| 225 | + *
|
|
| 226 | + * @param {boolean} isGenerating - Whether we are generating.
|
|
| 227 | + */
|
|
| 228 | + _setGenerating(isGenerating) {
|
|
| 229 | + this._generating = isGenerating;
|
|
| 230 | + this._updateGenerateButtonState();
|
|
| 231 | + this._connectingEl.classList.toggle("show-connecting", isGenerating);
|
|
| 232 | + },
|
|
| 233 | + |
|
| 234 | + /**
|
|
| 235 | + * Update the state of the generate button.
|
|
| 236 | + */
|
|
| 237 | + _updateGenerateButtonState() {
|
|
| 238 | + this._generateButton.disabled = this._generating || !this._remainingInvites;
|
|
| 239 | + },
|
|
| 240 | + |
|
| 241 | + /**
|
|
| 242 | + * Start generating a new invite.
|
|
| 243 | + */
|
|
| 244 | + _generateNewInvite() {
|
|
| 245 | + if (this._generating) {
|
|
| 246 | + console.error("Already generating an invite");
|
|
| 247 | + return;
|
|
| 248 | + }
|
|
| 249 | + this._setGenerating(true);
|
|
| 250 | + // Clear the previous error.
|
|
| 251 | + this._updateGenerateError(null);
|
|
| 252 | + // Move focus from the button to the connecting element, since button is
|
|
| 253 | + // now disabled.
|
|
| 254 | + this._connectingEl.focus();
|
|
| 255 | + |
|
| 256 | + let lostFocus = false;
|
|
| 257 | + Lox.generateInvite()
|
|
| 258 | + .finally(() => {
|
|
| 259 | + // Fetch whether the connecting label still has focus before we hide it.
|
|
| 260 | + lostFocus = this._connectingEl.contains(document.activeElement);
|
|
| 261 | + this._setGenerating(false);
|
|
| 262 | + })
|
|
| 263 | + .then(
|
|
| 264 | + invite => {
|
|
| 265 | + this._addInvite(invite);
|
|
| 266 | + |
|
| 267 | + if (!this._inviteListEl.contains(document.activeElement)) {
|
|
| 268 | + // Does not have focus, change the selected item to be the new
|
|
| 269 | + // invite (at index 0).
|
|
| 270 | + this._inviteListEl.selectedIndex = 0;
|
|
| 271 | + }
|
|
| 272 | + |
|
| 273 | + if (lostFocus) {
|
|
| 274 | + // Move focus to the new invite before we hide the "Connecting"
|
|
| 275 | + // message.
|
|
| 276 | + this._inviteListEl.focus();
|
|
| 277 | + }
|
|
| 278 | + |
|
| 279 | + // TODO: When Lox sends out notifications, let the observer handle the
|
|
| 280 | + // change rather than calling _updateRemainingInvites directly.
|
|
| 281 | + this._updateRemainingInvites();
|
|
| 282 | + },
|
|
| 283 | + loxError => {
|
|
| 284 | + console.error("Failed to generate an invite", loxError);
|
|
| 285 | + switch (loxError.type) {
|
|
| 286 | + case LoxErrors.LoxServerUnreachable:
|
|
| 287 | + this._updateGenerateError("no-server");
|
|
| 288 | + break;
|
|
| 289 | + default:
|
|
| 290 | + this._updateGenerateError("generic");
|
|
| 291 | + break;
|
|
| 292 | + }
|
|
| 293 | + |
|
| 294 | + if (lostFocus) {
|
|
| 295 | + // Move focus back to the button before we hide the "Connecting"
|
|
| 296 | + // message.
|
|
| 297 | + this._generateButton.focus();
|
|
| 298 | + }
|
|
| 299 | + }
|
|
| 300 | + );
|
|
| 301 | + },
|
|
| 302 | + |
|
| 303 | + /**
|
|
| 304 | + * Update the shown generation error.
|
|
| 305 | + *
|
|
| 306 | + * @param {string?} type - The error type, or null if no error should be
|
|
| 307 | + * shown.
|
|
| 308 | + */
|
|
| 309 | + _updateGenerateError(type) {
|
|
| 310 | + // First clear the existing error.
|
|
| 311 | + this._errorEl.removeAttribute("data-l10n-id");
|
|
| 312 | + this._errorEl.textContent = "";
|
|
| 313 | + this._errorEl.classList.toggle("show-error", !!type);
|
|
| 314 | + |
|
| 315 | + if (!type) {
|
|
| 316 | + return;
|
|
| 317 | + }
|
|
| 318 | + |
|
| 319 | + let errorId;
|
|
| 320 | + switch (type) {
|
|
| 321 | + case "no-server":
|
|
| 322 | + errorId = "lox-invite-dialog-no-server-error";
|
|
| 323 | + break;
|
|
| 324 | + case "generic":
|
|
| 325 | + // Generic error.
|
|
| 326 | + errorId = "lox-invite-dialog-generic-invite-error";
|
|
| 327 | + break;
|
|
| 328 | + }
|
|
| 329 | + |
|
| 330 | + document.l10n.setAttributes(this._errorEl, errorId);
|
|
| 331 | + },
|
|
| 332 | +};
|
|
| 333 | + |
|
| 334 | +window.addEventListener(
|
|
| 335 | + "DOMContentLoaded",
|
|
| 336 | + () => {
|
|
| 337 | + gLoxInvites.init();
|
|
| 338 | + window.addEventListener(
|
|
| 339 | + "unload",
|
|
| 340 | + () => {
|
|
| 341 | + gLoxInvites.uninit();
|
|
| 342 | + },
|
|
| 343 | + { once: true }
|
|
| 344 | + );
|
|
| 345 | + },
|
|
| 346 | + { once: true }
|
|
| 347 | +); |
| 1 | +<?xml version="1.0" encoding="UTF-8"?>
|
|
| 2 | +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
|
| 3 | +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
|
|
| 4 | +<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
|
|
| 5 | + |
|
| 6 | +<window
|
|
| 7 | + type="child"
|
|
| 8 | + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
|
| 9 | + xmlns:html="http://www.w3.org/1999/xhtml"
|
|
| 10 | + data-l10n-id="lox-invite-dialog-title"
|
|
| 11 | +>
|
|
| 12 | + <!-- Context menu, aria-hidden whilst not shown so it does not appear in the
|
|
| 13 | + - document content. -->
|
|
| 14 | + <menupopup id="lox-invite-dialog-item-menu" aria-hidden="true">
|
|
| 15 | + <menuitem
|
|
| 16 | + id="lox-invite-dialog-copy-menu-item"
|
|
| 17 | + data-l10n-id="lox-invite-dialog-menu-item-copy-invite"
|
|
| 18 | + />
|
|
| 19 | + </menupopup>
|
|
| 20 | + <dialog id="lox-invite-dialog" buttons="accept">
|
|
| 21 | + <linkset>
|
|
| 22 | + <html:link rel="localization" href="browser/tor-browser.ftl" />
|
|
| 23 | + </linkset>
|
|
| 24 | + |
|
| 25 | + <script src="chrome://browser/content/torpreferences/loxInviteDialog.js" />
|
|
| 26 | + |
|
| 27 | + <description data-l10n-id="lox-invite-dialog-description"></description>
|
|
| 28 | + <html:div id="lox-invite-dialog-generate-area">
|
|
| 29 | + <html:span id="lox-invite-dialog-remaining"></html:span>
|
|
| 30 | + <html:button
|
|
| 31 | + id="lox-invite-dialog-generate-button"
|
|
| 32 | + data-l10n-id="lox-invite-dialog-request-button"
|
|
| 33 | + ></html:button>
|
|
| 34 | + <html:div id="lox-invite-dialog-message-area">
|
|
| 35 | + <html:span
|
|
| 36 | + id="lox-invite-dialog-error-message"
|
|
| 37 | + role="alert"
|
|
| 38 | + ></html:span>
|
|
| 39 | + <html:span
|
|
| 40 | + id="lox-invite-dialog-connecting"
|
|
| 41 | + role="alert"
|
|
| 42 | + tabindex="0"
|
|
| 43 | + data-l10n-id="lox-invite-dialog-connecting"
|
|
| 44 | + ></html:span>
|
|
| 45 | + </html:div>
|
|
| 46 | + </html:div>
|
|
| 47 | + <html:div
|
|
| 48 | + id="lox-invite-dialog-list-label"
|
|
| 49 | + data-l10n-id="lox-invite-dialog-invites-label"
|
|
| 50 | + ></html:div>
|
|
| 51 | + <richlistbox
|
|
| 52 | + id="lox-invite-dialog-list"
|
|
| 53 | + aria-labelledby="lox-invite-dialog-list-label"
|
|
| 54 | + ></richlistbox>
|
|
| 55 | + </dialog>
|
|
| 56 | +</window> |
| ... | ... | @@ -820,6 +820,75 @@ dialog#torPreferences-requestBridge-dialog > hbox { |
| 820 | 820 | background: var(--qr-one);
|
| 821 | 821 | }
|
| 822 | 822 | |
| 823 | +/* Lox invite dialog */
|
|
| 824 | + |
|
| 825 | +#lox-invite-dialog-generate-area {
|
|
| 826 | + flex: 0 0 auto;
|
|
| 827 | + display: grid;
|
|
| 828 | + grid-template:
|
|
| 829 | + ". remaining button" min-content
|
|
| 830 | + "message message message" auto
|
|
| 831 | + / 1fr max-content max-content;
|
|
| 832 | + gap: 8px;
|
|
| 833 | + margin-block: 16px 8px;
|
|
| 834 | + align-items: center;
|
|
| 835 | +}
|
|
| 836 | + |
|
| 837 | +#lox-invite-dialog-remaining {
|
|
| 838 | + grid-area: remaining;
|
|
| 839 | +}
|
|
| 840 | + |
|
| 841 | +#lox-invite-dialog-generate-button {
|
|
| 842 | + grid-area: button;
|
|
| 843 | +}
|
|
| 844 | + |
|
| 845 | +#lox-invite-dialog-message-area {
|
|
| 846 | + grid-area: message;
|
|
| 847 | + justify-self: end;
|
|
| 848 | +}
|
|
| 849 | + |
|
| 850 | +#lox-invite-dialog-message-area::after {
|
|
| 851 | + /* Zero width space, to ensure we are always one line high. */
|
|
| 852 | + content: "\200B";
|
|
| 853 | +}
|
|
| 854 | + |
|
| 855 | +#lox-invite-dialog-error-message {
|
|
| 856 | + color: var(--in-content-error-text-color);
|
|
| 857 | +}
|
|
| 858 | + |
|
| 859 | +#lox-invite-dialog-error-message:not(.show-error) {
|
|
| 860 | + display: none;
|
|
| 861 | +}
|
|
| 862 | + |
|
| 863 | +#lox-invite-dialog-connecting {
|
|
| 864 | + color: var(--text-color-deemphasized);
|
|
| 865 | + /* TODO: Add spinner ::before */
|
|
| 866 | +}
|
|
| 867 | + |
|
| 868 | +#lox-invite-dialog-connecting:not(.show-connecting) {
|
|
| 869 | + display: none;
|
|
| 870 | +}
|
|
| 871 | + |
|
| 872 | +#lox-invite-dialog-list-label {
|
|
| 873 | + font-weight: 700;
|
|
| 874 | +}
|
|
| 875 | + |
|
| 876 | +#lox-invite-dialog-list {
|
|
| 877 | + flex: 1 1 auto;
|
|
| 878 | + /* basis height */
|
|
| 879 | + height: 10em;
|
|
| 880 | + margin-block: 8px;
|
|
| 881 | +}
|
|
| 882 | + |
|
| 883 | +.lox-invite-dialog-list-item {
|
|
| 884 | + white-space: nowrap;
|
|
| 885 | + overflow-x: hidden;
|
|
| 886 | + /* FIXME: ellipsis does not show. */
|
|
| 887 | + text-overflow: ellipsis;
|
|
| 888 | + padding-block: 6px;
|
|
| 889 | + padding-inline: 8px;
|
|
| 890 | +}
|
|
| 891 | + |
|
| 823 | 892 | /* Builtin bridge dialog */
|
| 824 | 893 | #torPreferences-builtinBridge-header {
|
| 825 | 894 | margin: 8px 0 10px 0;
|
| ... | ... | @@ -9,6 +9,8 @@ browser.jar: |
| 9 | 9 | content/browser/torpreferences/lox-success.svg (content/lox-success.svg)
|
| 10 | 10 | content/browser/torpreferences/lox-complete-ring.svg (content/lox-complete-ring.svg)
|
| 11 | 11 | content/browser/torpreferences/lox-progress-ring.svg (content/lox-progress-ring.svg)
|
| 12 | + content/browser/torpreferences/loxInviteDialog.xhtml (content/loxInviteDialog.xhtml)
|
|
| 13 | + content/browser/torpreferences/loxInviteDialog.js (content/loxInviteDialog.js)
|
|
| 12 | 14 | content/browser/torpreferences/bridgeQrDialog.xhtml (content/bridgeQrDialog.xhtml)
|
| 13 | 15 | content/browser/torpreferences/bridgeQrDialog.js (content/bridgeQrDialog.js)
|
| 14 | 16 | content/browser/torpreferences/builtinBridgeDialog.xhtml (content/builtinBridgeDialog.xhtml)
|
| ... | ... | @@ -296,3 +296,17 @@ user-provide-bridge-dialog-result-invite = The following bridges were shared wit |
| 296 | 296 | user-provide-bridge-dialog-result-addresses = The following bridges were entered by you.
|
| 297 | 297 | user-provide-bridge-dialog-next-button =
|
| 298 | 298 | .label = Next
|
| 299 | + |
|
| 300 | +## Bridge pass invite dialog. Temporary.
|
|
| 301 | + |
|
| 302 | +lox-invite-dialog-title =
|
|
| 303 | + .title = Bridge pass invites
|
|
| 304 | +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.
|
|
| 305 | +lox-invite-dialog-request-button = Request new invite
|
|
| 306 | +lox-invite-dialog-connecting = Connecting to bridge pass server…
|
|
| 307 | +lox-invite-dialog-no-server-error = Unable to connect to bridge pass server.
|
|
| 308 | +lox-invite-dialog-generic-invite-error = Failed to create a new invite.
|
|
| 309 | +lox-invite-dialog-invites-label = Created invites:
|
|
| 310 | +lox-invite-dialog-menu-item-copy-invite =
|
|
| 311 | + .label = Copy invite
|
|
| 312 | + .accesskey = C |