[tor-commits] [Git][tpo/applications/tor-browser][tor-browser-128.1.0esr-14.0-1] 5 commits: fixup! Bug 30237: Add v3 onion services client authentication prompt

Pier Angelo Vendrame (@pierov) git at gitlab.torproject.org
Tue Aug 20 13:11:06 UTC 2024



Pier Angelo Vendrame pushed to branch tor-browser-128.1.0esr-14.0-1 at The Tor Project / Applications / Tor Browser


Commits:
ace2c34b by Henry Wilkes at 2024-08-20T12:55:05+00:00
fixup! Bug 30237: Add v3 onion services client authentication prompt

Bug 42212: Migrate onion service strings to Fluent.

+ Use "onion site" instead of "onionsite" or "onion service".
+ Use sentence case by default.
+ Changed the prompt accept button from "Done" to "OK", in line with
  other prompts.
+ Re-ordered the saved keys dialog introduction text from "Keys for the
  following onionsite are..." to "The following onion site keys are..."
+ Use bold text for the prompt title, rather than just the onion site
  part.
+ Clear the error message in the saved key dialog whenever the user
  tries to remove a key (again).
+ Other small tidies in the touched areas.

- - - - -
611b5828 by Henry Wilkes at 2024-08-20T12:55:05+00:00
fixup! Bug 23247: Communicating security expectations for .onion

Bug 42212: Migrate onion service strings to Fluent.

- - - - -
0e22809c by Henry Wilkes at 2024-08-20T12:55:05+00:00
fixup! Tor Browser strings

Bug 42212: Migrate onion service strings to Fluent.

- - - - -
35b2a147 by Henry Wilkes at 2024-08-20T12:55:05+00:00
fixup! Add TorStrings module for localization

Bug 42212: Migrate onion service strings to Fluent.

- - - - -
7143db9a by Henry Wilkes at 2024-08-20T12:55:05+00:00
fixup! Tor Browser localization migration scripts.

Bug 42212: Migrate onion service strings to Fluent.

- - - - -


16 changed files:

- browser/base/content/browser.xhtml
- browser/base/content/pageinfo/pageInfo.xhtml
- browser/base/content/pageinfo/security.js
- browser/components/onionservices/content/authNotificationIcon.inc.xhtml
- browser/components/onionservices/content/authPopup.inc.xhtml
- browser/components/onionservices/content/authPreferences.inc.xhtml
- browser/components/onionservices/content/authPreferences.js
- browser/components/onionservices/content/authPrompt.js
- browser/components/onionservices/content/onionservices.css
- browser/components/onionservices/content/savedKeysDialog.js
- browser/components/onionservices/content/savedKeysDialog.xhtml
- toolkit/locales/en-US/toolkit/global/tor-browser.ftl
- toolkit/modules/TorStrings.sys.mjs
- − toolkit/torbutton/chrome/locale/en-US/torbutton.dtd
- toolkit/torbutton/chrome/locale/en-US/torbutton.properties
- + tools/torbrowser/l10n/migrations/bug-42212-onion-services.py


Changes:

=====================================
browser/base/content/browser.xhtml
=====================================
@@ -6,11 +6,6 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-<!DOCTYPE window [
-  <!ENTITY % torbuttonDTD SYSTEM "chrome://torbutton/locale/torbutton.dtd">
-%torbuttonDTD;
-]>
-
 <html id="main-window"
         xmlns:html="http://www.w3.org/1999/xhtml"
         xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"


=====================================
browser/base/content/pageinfo/pageInfo.xhtml
=====================================
@@ -27,6 +27,7 @@
     <html:link rel="stylesheet" href="chrome://browser/skin/pageInfo.css" />
 
     <html:link rel="localization" href="browser/pageInfo.ftl"/>
+    <html:link rel="localization" href="toolkit/global/tor-browser.ftl"/>
   </linkset>
   #ifdef XP_MACOSX
   #include ../macWindow.inc.xhtml


=====================================
browser/base/content/pageinfo/security.js
=====================================
@@ -15,11 +15,6 @@ const { DownloadUtils } = ChromeUtils.importESModule(
 ChromeUtils.defineESModuleGetters(this, {
   LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
 });
-ChromeUtils.defineLazyGetter(this, "gTorButtonBundle", function () {
-  return Services.strings.createBundle(
-    "chrome://torbutton/locale/torbutton.properties"
-  );
-});
 
 var security = {
   async init(uri, windowInfo) {
@@ -364,9 +359,13 @@ async function securityOnLoad(uri, windowInfo) {
       );
     } else {
       try {
-        hdr = gTorButtonBundle.formatStringFromName(
-          "pageInfo_OnionEncryptionWithBitsAndProtocol",
-          [info.encryptionAlgorithm, info.encryptionStrength + "", info.version]
+        hdr = await document.l10n.formatValue(
+          "page-info-onion-site-encryption-with-bits",
+          {
+            "encryption-type": info.encryptionAlgorithm,
+            "encryption-strength": info.encryptionStrength,
+            "encryption-version": info.version,
+          }
         );
       } catch (err) {
         hdr =
@@ -392,11 +391,9 @@ async function securityOnLoad(uri, windowInfo) {
     }
     msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
   } else {
-    try {
-      hdr = gTorButtonBundle.GetStringFromName("pageInfo_OnionEncryption");
-    } catch (err) {
-      hdr = "Connection Encrypted (Onion Service)";
-    }
+    hdr = await document.l10n.formatValue(
+      "page-info-onion-site-encryption-plain"
+    );
 
     msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1");
     msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2");


=====================================
browser/components/onionservices/content/authNotificationIcon.inc.xhtml
=====================================
@@ -3,4 +3,4 @@
 <image id="tor-clientauth-notification-icon"
        class="notification-anchor-icon tor-clientauth-icon"
        role="button"
-       tooltiptext="&torbutton.onionServices.authPrompt.tooltip;"/>
+       data-l10n-id="onion-site-authentication-urlbar-button"/>


=====================================
browser/components/onionservices/content/authPopup.inc.xhtml
=====================================
@@ -2,17 +2,25 @@
 
 <popupnotification id="tor-clientauth-notification" hidden="true">
   <popupnotificationcontent orient="vertical">
-    <description id="tor-clientauth-notification-desc"/>
-    <label id="tor-clientauth-notification-learnmore"
-           class="text-link popup-notification-learnmore-link"
-           is="text-link"
-           href="about:manual#onion-services_onion-service-authentication"
-           useoriginprincipal="true"/>
+    <description id="tor-clientauth-notification-desc" />
+    <label
+      class="text-link popup-notification-learnmore-link"
+      is="text-link"
+      href="about:manual#onion-services_onion-service-authentication"
+      useoriginprincipal="true"
+      data-l10n-id="onion-site-authentication-prompt-learn-more"
+    />
     <html:div>
-      <html:input id="tor-clientauth-notification-key" type="password"/>
-      <html:div id="tor-clientauth-warning"/>
-      <checkbox id="tor-clientauth-persistkey-checkbox"
-                label="&torbutton.onionServices.authPrompt.persistCheckboxLabel;"/>
+      <html:input
+        id="tor-clientauth-notification-key"
+        type="password"
+        data-l10n-id="onion-site-authentication-prompt-key-input"
+      />
+      <html:div id="tor-clientauth-warning"></html:div>
+      <checkbox
+        id="tor-clientauth-persistkey-checkbox"
+        data-l10n-id="onion-site-authentication-prompt-remember-checkbox"
+      />
     </html:div>
   </popupnotificationcontent>
 </popupnotification>


=====================================
browser/components/onionservices/content/authPreferences.inc.xhtml
=====================================
@@ -2,21 +2,34 @@
 
 <groupbox id="torOnionServiceKeys" orient="vertical"
           data-category="panePrivacy" hidden="true">
-  <label><html:h2 id="torOnionServiceKeys-header"/></label>
+  <label><html:h2
+    data-l10n-id="onion-site-authentication-preferences-heading"
+  ></html:h2></label>
   <hbox>
     <description
       class="description-deemphasized description-with-side-element"
       flex="1"
     >
-      <html:span id="torOnionServiceKeys-overview"
-                 class="tail-with-learn-more"/>
-      <label id="torOnionServiceKeys-learnMore" class="learnMore text-link"
-             is="text-link"/>
+      <html:span
+        id="torOnionServiceKeys-overview"
+        class="tail-with-learn-more"
+        data-l10n-id="onion-site-authentication-preferences-overview"
+      ></html:span>
+      <label
+        id="torOnionServiceKeys-learnMore"
+        class="learnMore text-link"
+        is="text-link"
+        href="about:manual#onion-services_onion-service-authentication"
+        useoriginprincipal="true"
+        data-l10n-id="onion-site-authentication-preferences-learn-more"
+      />
     </description>
     <vbox align="end">
-      <button id="torOnionServiceKeys-savedKeys"
-              is="highlightable-button"
-              class="accessory-button"/>
+      <html:button
+        id="torOnionServiceKeys-savedKeys"
+        class="accessory-button"
+        data-l10n-id="onion-site-authentication-preferences-saved-keys-button"
+      ></html:button>
     </vbox>
   </hbox>
 </groupbox>


=====================================
browser/components/onionservices/content/authPreferences.js
=====================================
@@ -2,69 +2,19 @@
 
 "use strict";
 
-ChromeUtils.defineESModuleGetters(this, {
-  TorStrings: "resource://gre/modules/TorStrings.sys.mjs",
-});
-
-/* globals gSubDialog */
-
-/*
-  Onion Services Client Authentication Preferences Code
-
-  Code to handle init and update of onion services authentication section
-  in about:preferences#privacy
-*/
-
-const OnionServicesAuthPreferences = {
-  selector: {
-    groupBox: "#torOnionServiceKeys",
-    header: "#torOnionServiceKeys-header",
-    overview: "#torOnionServiceKeys-overview",
-    learnMore: "#torOnionServiceKeys-learnMore",
-    savedKeysButton: "#torOnionServiceKeys-savedKeys",
-  },
+/* import-globals-from /browser/components/preferences/preferences.js */
 
+/**
+ * Onion site preferences.
+ */
+var OnionServicesAuthPreferences = {
   init() {
-    // populate XUL with localized strings
-    this._populateXUL();
-  },
-
-  _populateXUL() {
-    const groupbox = document.querySelector(this.selector.groupBox);
-
-    let elem = groupbox.querySelector(this.selector.header);
-    elem.textContent = TorStrings.onionServices.authPreferences.header;
-
-    elem = groupbox.querySelector(this.selector.overview);
-    elem.textContent = TorStrings.onionServices.authPreferences.overview;
-
-    elem = groupbox.querySelector(this.selector.learnMore);
-    elem.setAttribute("value", TorStrings.onionServices.learnMore);
-    elem.setAttribute(
-      "href",
-      "about:manual#onion-services_onion-service-authentication"
-    );
-    elem.setAttribute("useoriginprincipal", "true");
-
-    elem = groupbox.querySelector(this.selector.savedKeysButton);
-    elem.setAttribute(
-      "label",
-      TorStrings.onionServices.authPreferences.savedKeys
-    );
-    elem.addEventListener("command", () =>
-      OnionServicesAuthPreferences.onViewSavedKeys()
-    );
+    document
+      .getElementById("torOnionServiceKeys-savedKeys")
+      .addEventListener("click", () => {
+        gSubDialog.open(
+          "chrome://browser/content/onionservices/savedKeysDialog.xhtml"
+        );
+      });
   },
-
-  onViewSavedKeys() {
-    gSubDialog.open(
-      "chrome://browser/content/onionservices/savedKeysDialog.xhtml"
-    );
-  },
-}; // OnionServicesAuthPreferences
-
-Object.defineProperty(this, "OnionServicesAuthPreferences", {
-  value: OnionServicesAuthPreferences,
-  enumerable: true,
-  writable: false,
-});
+};


=====================================
browser/components/onionservices/content/authPrompt.js
=====================================
@@ -56,28 +56,47 @@ var OnionAuthPrompt = {
   show(details) {
     this._logger.debug(`New Notification: ${this._detailsRepr(details)}`);
 
+    // NOTE: PopupNotifications currently requires the accesskey and label to be
+    // set for all actions, and does not accept fluent IDs in their place.
+    // Moreover, there doesn't appear to be a simple way to work around this, so
+    // we have to fetch the strings here before calling the show() method.
+    // NOTE: We avoid using the async formatMessages because we don't want to
+    // race against the browser's location changing.
+    // In principle, we could check that the details.browser.currentURI still
+    // matches details.uri or use a LocationChange listener. However, we expect
+    // that PopupNotifications will eventually change to accept fluent IDs, so
+    // we won't have to use formatMessages here at all.
+    // Moreover, we do not expect this notification to be common, so this
+    // shouldn't be too expensive.
+    // NOTE: Once we call PopupNotifications.show, PopupNotifications should
+    // take care of listening for changes in locations for us and remove the
+    // notification.
+    let [okButtonMsg, cancelButtonMsg] = this._lazy.SyncL10n.formatMessagesSync(
+      [
+        "onion-site-authentication-prompt-ok-button",
+        "onion-site-authentication-prompt-cancel-button",
+      ]
+    );
+
+    // Get an attribute string from a L10nMessage.
+    // We wrap the return value as a String to prevent the notification from
+    // throwing (and not showing) if a locale is unexpectedly missing a value.
+    const msgAttribute = (msg, name) =>
+      String((msg.attributes ?? []).find(attr => attr.name === name)?.value);
+
     let mainAction = {
-      label: this.TorStrings.onionServices.authPrompt.done,
-      accessKey: this.TorStrings.onionServices.authPrompt.doneAccessKey,
+      label: msgAttribute(okButtonMsg, "label"),
+      accessKey: msgAttribute(okButtonMsg, "accesskey"),
       leaveOpen: true, // Callback is responsible for closing the notification.
-      callback: this._onDone.bind(this),
+      callback: () => this._onDone(),
     };
 
-    let dialogBundle = Services.strings.createBundle(
-      "chrome://global/locale/dialog.properties"
-    );
-
-    let cancelAccessKey = dialogBundle.GetStringFromName("accesskey-cancel");
-    if (!cancelAccessKey) {
-      cancelAccessKey = "c";
-    } // required by PopupNotifications.show()
-
     // The first secondarybuttoncommand (cancelAction) should be triggered when
     // the user presses "Escape".
     let cancelAction = {
-      label: dialogBundle.GetStringFromName("button-cancel"),
-      accessKey: cancelAccessKey,
-      callback: this._onCancel.bind(this),
+      label: msgAttribute(cancelButtonMsg, "label"),
+      accessKey: msgAttribute(cancelButtonMsg, "accesskey"),
+      callback: () => this._onCancel(),
     };
 
     let options = {
@@ -134,22 +153,17 @@ var OnionAuthPrompt = {
     this._keyInput.value = "";
     this._persistCheckbox.checked = false;
 
-    // Handle replacement of the onion name within the localized
-    // string ourselves so we can show the onion name as bold text.
-    // We do this by splitting the localized string and creating
-    // several HTML <span> elements.
-    const fmtString = this.TorStrings.onionServices.authPrompt.description;
-    const [prefix, suffix] = fmtString.split("%S");
-
-    const domainEl = document.createElement("span");
-    domainEl.id = "tor-clientauth-notification-onionname";
-    domainEl.textContent = TorUIUtils.shortenOnionAddress(
-      this._shownDetails?.onionHost ?? ""
+    document.l10n.setAttributes(
+      this._descriptionEl,
+      "onion-site-authentication-prompt-description",
+      {
+        onionsite: TorUIUtils.shortenOnionAddress(
+          this._shownDetails?.onionHost ?? ""
+        ),
+      }
     );
 
-    this._descriptionEl.replaceChildren(prefix, domainEl, suffix);
-
-    this._showWarning(undefined);
+    this._showWarning(null);
   },
 
   /**
@@ -187,12 +201,13 @@ var OnionAuthPrompt = {
     );
 
     // Grab the details before they might change as we await.
-    const { browser, onionServiceId, notification } = this._shownDetails;
+    const details = this._shownDetails;
+    const { browser, onionServiceId, notification } = details;
     const isPermanent = this._persistCheckbox.checked;
 
     const base64key = this._keyToBase64(this._keyInput.value);
     if (!base64key) {
-      this._showWarning(this.TorStrings.onionServices.authPrompt.invalidKey);
+      this._showWarning("onion-site-authentication-prompt-invalid-key");
       return;
     }
 
@@ -200,12 +215,11 @@ var OnionAuthPrompt = {
       const provider = await this._lazy.TorProviderBuilder.build();
       await provider.onionAuthAdd(onionServiceId, base64key, isPermanent);
     } catch (e) {
-      if (e.torMessage) {
-        this._showWarning(e.torMessage);
-      } else {
-        this._logger.error(`Failed to set key for ${onionServiceId}`, e);
+      this._logger.error(`Failed to set key for ${onionServiceId}`, e);
+      if (details === this._shownDetails) {
+        // Notification has not been replaced.
         this._showWarning(
-          this.TorStrings.onionServices.authPrompt.failedToSetKey
+          "onion-site-authentication-prompt-setting-key-failed"
         );
       }
       return;
@@ -245,16 +259,19 @@ var OnionAuthPrompt = {
   /**
    * Show a warning message to the user or clear the warning.
    *
-   * @param {string?} warningMessage - The message to show, or undefined to
-   *   clear the current message.
+   * @param {?string} warningMessageId - The l10n ID for the message to show, or
+   *   null to clear the current message.
    */
-  _showWarning(warningMessage) {
-    this._logger.debug(`Showing warning: ${warningMessage}`);
-    if (warningMessage) {
-      this._warningEl.textContent = warningMessage;
+  _showWarning(warningMessageId) {
+    this._logger.debug(`Showing warning: ${warningMessageId}`);
+    if (warningMessageId) {
+      document.l10n.setAttributes(this._warningEl, warningMessageId);
       this._warningEl.removeAttribute("hidden");
       this._keyInput.classList.add("invalid");
     } else {
+      // Clean up.
+      this._warningEl.removeAttribute("data-l10n-id");
+      this._warningEl.textContent = "";
       this._warningEl.setAttribute("hidden", "true");
       this._keyInput.classList.remove("invalid");
     }
@@ -264,7 +281,7 @@ var OnionAuthPrompt = {
    * Convert the user-entered key into base64.
    *
    * @param {string} keyString - The key to convert.
-   * @returns {string?} - The base64 representation, or undefined if the given
+   * @returns {?string} - The base64 representation, or undefined if the given
    *   key was not the correct format.
    */
   _keyToBase64(keyString) {
@@ -310,14 +327,17 @@ var OnionAuthPrompt = {
       maxLogLevelPref: "browser.onionAuthPrompt.loglevel",
     });
 
-    const { TorStrings } = ChromeUtils.importESModule(
-      "resource://gre/modules/TorStrings.sys.mjs"
-    );
-    this.TorStrings = TorStrings;
     ChromeUtils.defineESModuleGetters(this._lazy, {
       TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
       CommonUtils: "resource://services-common/utils.sys.mjs",
     });
+    // Allow synchornous access to the localized strings. Used only for the
+    // button actions, which is currently a hard requirement for
+    // PopupNotifications.show. Hopefully, PopupNotifications will accept fluent
+    // ids in their place, or get replaced with something else that does.
+    ChromeUtils.defineLazyGetter(this._lazy, "SyncL10n", () => {
+      return new Localization(["toolkit/global/tor-browser.ftl"], true);
+    });
 
     this._keyInput = document.getElementById("tor-clientauth-notification-key");
     this._persistCheckbox = document.getElementById(
@@ -328,19 +348,6 @@ var OnionAuthPrompt = {
       "tor-clientauth-notification-desc"
     );
 
-    // Set "Learn More" label and href.
-    const learnMoreElem = document.getElementById(
-      "tor-clientauth-notification-learnmore"
-    );
-    learnMoreElem.setAttribute(
-      "value",
-      this.TorStrings.onionServices.learnMore
-    );
-
-    this._keyInput.setAttribute(
-      "placeholder",
-      this.TorStrings.onionServices.authPrompt.keyPlaceholder
-    );
     this._keyInput.addEventListener("keydown", event => {
       if (event.key === "Enter") {
         event.preventDefault();
@@ -349,7 +356,7 @@ var OnionAuthPrompt = {
     });
     this._keyInput.addEventListener("input", () => {
       // Remove the warning.
-      this._showWarning(undefined);
+      this._showWarning(null);
     });
 
     // Force back focus on click: tor-browser#41856


=====================================
browser/components/onionservices/content/onionservices.css
=====================================
@@ -1,12 +1,10 @@
 /* Copyright (c) 2020, The Tor Project, Inc. */
 
- at namespace html url("http://www.w3.org/1999/xhtml");
-
-html|*#tor-clientauth-notification-onionname {
+#tor-clientauth-notification-desc {
   font-weight: bold;
 }
 
-html|*#tor-clientauth-notification-key {
+#tor-clientauth-notification-key {
   box-sizing: border-box;
   width: 100%;
   margin-top: 15px;
@@ -17,12 +15,12 @@ html|*#tor-clientauth-notification-key {
  * browser/components/newtab/css/activity-stream-mac.css (linux and windows
  * use the same rules).
  */
-html|*#tor-clientauth-notification-key.invalid {
+#tor-clientauth-notification-key.invalid {
   border: 1px solid #D70022;
   box-shadow: 0 0 0 1px #D70022, 0 0 0 4px rgba(215, 0, 34, 0.3);
 }
 
-html|*#tor-clientauth-warning {
+#tor-clientauth-warning {
   display: inline-block;
   animation: fade-up-tt 450ms;
   background: #D70022;
@@ -35,11 +33,11 @@ html|*#tor-clientauth-warning {
   z-index: 1;
 }
 
-html|*#tor-clientauth-warning[hidden] {
+#tor-clientauth-warning[hidden] {
   display: none;
 }
 
-html|*#tor-clientauth-warning::before {
+#tor-clientauth-warning::before {
   background: #D70022;
   bottom: -8px;
   content: '.';


=====================================
browser/components/onionservices/content/savedKeysDialog.js
=====================================
@@ -3,23 +3,10 @@
 "use strict";
 
 ChromeUtils.defineESModuleGetters(this, {
-  TorStrings: "resource://gre/modules/TorStrings.sys.mjs",
   TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
 });
 
 var gOnionServicesSavedKeysDialog = {
-  selector: {
-    dialog: "#onionservices-savedkeys-dialog",
-    intro: "#onionservices-savedkeys-intro",
-    tree: "#onionservices-savedkeys-tree",
-    onionSiteCol: "#onionservices-savedkeys-siteCol",
-    onionKeyCol: "#onionservices-savedkeys-keyCol",
-    errorIcon: "#onionservices-savedkeys-errorIcon",
-    errorMessage: "#onionservices-savedkeys-errorMessage",
-    removeButton: "#onionservices-savedkeys-remove",
-    removeAllButton: "#onionservices-savedkeys-removeall",
-  },
-
   _tree: undefined,
   _busyCount: 0,
   get _isBusy() {
@@ -27,8 +14,8 @@ var gOnionServicesSavedKeysDialog = {
     return this._busyCount > 0;
   },
 
-  // Public functions (called from outside this file).
-  async deleteSelectedKeys() {
+  async _deleteSelectedKeys() {
+    this._showError(null);
     this._withBusy(async () => {
       const indexesToDelete = [];
       const count = this._tree.view.selection.getRangeCount();
@@ -42,8 +29,6 @@ var gOnionServicesSavedKeysDialog = {
       }
 
       if (indexesToDelete.length) {
-        const controllerFailureMsg =
-          TorStrings.onionServices.authPreferences.failedToRemoveKey;
         const provider = await TorProviderBuilder.build();
         try {
           // Remove in reverse index order to avoid issues caused by index
@@ -53,28 +38,23 @@ var gOnionServicesSavedKeysDialog = {
           }
         } catch (e) {
           console.error("Removing a saved key failed", e);
-          if (e.torMessage) {
-            this._showError(e.torMessage);
-          } else {
-            this._showError(controllerFailureMsg);
-          }
+          this._showError(
+            "onion-site-saved-keys-dialog-remove-keys-error-message"
+          );
         }
       }
     });
   },
 
-  async deleteAllKeys() {
+  async _deleteAllKeys() {
     this._tree.view.selection.selectAll();
-    await this.deleteSelectedKeys();
+    await this._deleteSelectedKeys();
   },
 
-  updateButtonsState() {
+  _updateButtonsState() {
     const haveSelection = this._tree.view.selection.getRangeCount() > 0;
-    const dialog = document.querySelector(this.selector.dialog);
-    const removeSelectedBtn = dialog.querySelector(this.selector.removeButton);
-    removeSelectedBtn.disabled = this._isBusy || !haveSelection;
-    const removeAllBtn = dialog.querySelector(this.selector.removeAllButton);
-    removeAllBtn.disabled = this._isBusy || this.rowCount === 0;
+    this._removeButton.disabled = this._isBusy || !haveSelection;
+    this._removeAllButton.disabled = this._isBusy || this.rowCount === 0;
   },
 
   // Private functions.
@@ -82,38 +62,40 @@ var gOnionServicesSavedKeysDialog = {
     document.mozSubdialogReady = this._init();
   },
 
-  async _init() {
+  _init() {
     this._populateXUL();
     window.addEventListener("keypress", this._onWindowKeyPress.bind(this));
     this._loadSavedKeys();
   },
 
   _populateXUL() {
-    const dialog = document.querySelector(this.selector.dialog);
-    const authPrefStrings = TorStrings.onionServices.authPreferences;
-    dialog.setAttribute("title", authPrefStrings.dialogTitle);
-
-    let elem = dialog.querySelector(this.selector.intro);
-    elem.textContent = authPrefStrings.dialogIntro;
-
-    elem = dialog.querySelector(this.selector.onionSiteCol);
-    elem.setAttribute("label", authPrefStrings.onionSite);
-
-    elem = dialog.querySelector(this.selector.onionKeyCol);
-    elem.setAttribute("label", authPrefStrings.onionKey);
-
-    elem = dialog.querySelector(this.selector.removeButton);
-    elem.setAttribute("label", authPrefStrings.remove);
-
-    elem = dialog.querySelector(this.selector.removeAllButton);
-    elem.setAttribute("label", authPrefStrings.removeAll);
+    this._errorMessageContainer = document.getElementById(
+      "onionservices-savedkeys-errorContainer"
+    );
+    this._errorMessageEl = document.getElementById(
+      "onionservices-savedkeys-errorMessage"
+    );
+    this._removeButton = document.getElementById(
+      "onionservices-savedkeys-remove"
+    );
+    this._removeButton.addEventListener("click", () => {
+      this._deleteSelectedKeys();
+    });
+    this._removeAllButton = document.getElementById(
+      "onionservices-savedkeys-removeall"
+    );
+    this._removeButton.addEventListener("click", () => {
+      this._deleteAllKeys();
+    });
 
-    this._tree = dialog.querySelector(this.selector.tree);
+    this._tree = document.getElementById("onionservices-savedkeys-tree");
+    this._tree.addEventListener("select", () => {
+      this._updateButtonsState();
+    });
   },
 
   async _loadSavedKeys() {
-    const controllerFailureMsg =
-      TorStrings.onionServices.authPreferences.failedToGetKeys;
+    this._showError(null);
     this._withBusy(async () => {
       try {
         this._tree.view = this;
@@ -139,11 +121,10 @@ var gOnionServicesSavedKeysDialog = {
         // Render the tree content.
         this._tree.rowCountChanged(0, this.rowCount);
       } catch (e) {
-        if (e.torMessage) {
-          this._showError(e.torMessage);
-        } else {
-          this._showError(controllerFailureMsg);
-        }
+        console.error("Failed to load keys", e);
+        this._showError(
+          "onion-site-saved-keys-dialog-fetch-keys-error-message"
+        );
       }
     });
   },
@@ -160,14 +141,14 @@ var gOnionServicesSavedKeysDialog = {
   async _withBusy(func) {
     this._busyCount++;
     if (this._busyCount === 1) {
-      this.updateButtonsState();
+      this._updateButtonsState();
     }
     try {
       await func();
     } finally {
       this._busyCount--;
       if (this._busyCount === 0) {
-        this.updateButtonsState();
+        this._updateButtonsState();
       }
     }
   },
@@ -179,16 +160,25 @@ var gOnionServicesSavedKeysDialog = {
     if (event.keyCode === KeyEvent.DOM_VK_ESCAPE) {
       window.close();
     } else if (event.keyCode === KeyEvent.DOM_VK_DELETE) {
-      this.deleteSelectedKeys();
+      this._deleteSelectedKeys();
     }
   },
 
-  _showError(aMessage) {
-    document
-      .getElementById("onionservices-savedkeys-errorContainer")
-      .classList.toggle("show-error", !!aMessage);
-    const errorDesc = document.querySelector(this.selector.errorMessage);
-    errorDesc.textContent = aMessage ? aMessage : "";
+  /**
+   * Show an error, or clear it.
+   *
+   * @param {?string} messageId - The l10n ID of the message to show, or null to
+   *   clear it.
+   */
+  _showError(messageId) {
+    this._errorMessageContainer.classList.toggle("show-error", !!messageId);
+    if (messageId) {
+      document.l10n.setAttributes(this._errorMessageEl, messageId);
+    } else {
+      // Clean up.
+      this._errorMessageEl.removeAttribute("data-l10n-id");
+      this._errorMessageEl.textContent = "";
+    }
   },
 
   // XUL tree widget view implementation.


=====================================
browser/components/onionservices/content/savedKeysDialog.xhtml
=====================================
@@ -9,29 +9,37 @@
   id="onionservices-savedkeys-dialog"
   windowtype="OnionServices:SavedKeys"
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  xmlns:html="http://www.w3.org/1999/xhtml"
+  data-l10n-id="onion-site-saved-keys-dialog-title"
 >
+  <linkset>
+    <html:link rel="localization" href="toolkit/global/tor-browser.ftl" />
+  </linkset>
+
   <script src="chrome://browser/content/onionservices/savedKeysDialog.js" />
 
   <vbox id="onionservices-savedkeys" class="contentPane" flex="1">
     <label
       id="onionservices-savedkeys-intro"
       control="onionservices-savedkeys-tree"
+      data-l10n-id="onion-site-saved-keys-dialog-intro"
     />
     <separator class="thin" />
-    <tree
-      id="onionservices-savedkeys-tree"
-      flex="1"
-      hidecolumnpicker="true"
-      onselect="gOnionServicesSavedKeysDialog.updateButtonsState();"
-    >
+    <tree id="onionservices-savedkeys-tree" flex="1" hidecolumnpicker="true">
       <treecols>
         <treecol
           id="onionservices-savedkeys-siteCol"
           flex="1"
           persist="width"
+          data-l10n-id="onion-site-saved-keys-dialog-table-header-site"
         />
         <splitter class="tree-splitter" />
-        <treecol id="onionservices-savedkeys-keyCol" flex="1" persist="width" />
+        <treecol
+          id="onionservices-savedkeys-keyCol"
+          flex="1"
+          persist="width"
+          data-l10n-id="onion-site-saved-keys-dialog-table-header-key"
+        />
       </treecols>
       <treechildren />
     </tree>
@@ -41,15 +49,15 @@
     </hbox>
     <separator class="thin" />
     <hbox id="onionservices-savedkeys-buttons">
-      <button
+      <html:button
         id="onionservices-savedkeys-remove"
         disabled="true"
-        oncommand="gOnionServicesSavedKeysDialog.deleteSelectedKeys();"
-      />
-      <button
+        data-l10n-id="onion-site-saved-keys-dialog-remove-button"
+      ></html:button>
+      <html:button
         id="onionservices-savedkeys-removeall"
-        oncommand="gOnionServicesSavedKeysDialog.deleteAllKeys();"
-      />
+        data-l10n-id="onion-site-saved-keys-dialog-remove-all-button"
+      ></html:button>
     </hbox>
   </vbox>
 </window>


=====================================
toolkit/locales/en-US/toolkit/global/tor-browser.ftl
=====================================
@@ -637,3 +637,62 @@ onion-neterror-invalid-address-description = The provided onion site address is
 # "Circuit" refers to a Tor network circuit.
 onion-neterror-timed-out-header = Onion site circuit creation timed out
 onion-neterror-timed-out-description = Failed to connect to the onion site, possibly due to a poor network connection.
+
+## Onion site authentication prompt.
+## "Onion site" is an abbreviation of "onion website": a website whose domain URL ends in ".onion", which is reachable through the Tor network.
+
+# Here "open" is a verb.
+onion-site-authentication-urlbar-button =
+    .tooltiptext = Open the onion site authentication panel
+
+# $onionsite (String) - The shortened onion address for the current web site, for example "a3jx8k…pbsh7y.onion".
+onion-site-authentication-prompt-description = The onion site { $onionsite } is requesting that you authenticate.
+onion-site-authentication-prompt-learn-more = Learn more
+onion-site-authentication-prompt-key-input =
+    .placeholder = Enter your private key for this onion site
+onion-site-authentication-prompt-remember-checkbox =
+    .label = Remember this key
+onion-site-authentication-prompt-ok-button =
+    .label = OK
+    .accesskey = O
+onion-site-authentication-prompt-cancel-button =
+    .label = Cancel
+    .accesskey = C
+# Error message shown when the entered key is the wrong length or the wrong format.
+# "base32" and "base64" are technical names for binary-to-text encodings: https://en.wikipedia.org/wiki/Base64
+onion-site-authentication-prompt-invalid-key = Please enter a valid key (52 base32 characters or 44 base64 characters)
+onion-site-authentication-prompt-setting-key-failed = Unable to configure Tor with your key
+
+## Page Info window for onion sites.
+## "Onion site" is an abbreviation of "onion website": a website whose domain URL ends in ".onion", which is reachable through the Tor network.
+
+# $encryption-type (String) - The name of the encryption algorithm used for the page, for example "TLS_AES_128_GCM_SHA256".
+# $encryption-stength (Number) - The number of bits for the encryption key, for example "128" or "256". The "NUMBER" function will format this number to the same locale, and the "useGrouping" option will remove grouping symbols, like thousand separators. Note that the "bit" in "bit keys" refers to a computer "binary digit".
+# $encryption-version (String) - The name and version of the encryption, for example "TLS 1.3".
+page-info-onion-site-encryption-with-bits = Connection encrypted (Onion site, { $encryption-type }, { NUMBER($encryption-strength, useGrouping: "false") } bit keys, { $encryption-version })
+page-info-onion-site-encryption-plain = Connection encrypted (Onion site)
+
+## Onion site authentication preferences.
+## "Onion site" is an abbreviation of "onion website": a website whose domain URL ends in ".onion", which is reachable through the Tor network.
+
+onion-site-authentication-preferences-heading = Onion site authentication
+onion-site-authentication-preferences-overview = Some onion sites require that you identify yourself with a key (a kind of password) before you can access them.
+onion-site-authentication-preferences-learn-more = Learn more
+onion-site-authentication-preferences-saved-keys-button = Saved keys…
+
+## Onion site saved keys dialog.
+## "Onion site" is an abbreviation of "onion website": a website whose domain URL ends in ".onion", which is reachable through the Tor network.
+
+onion-site-saved-keys-dialog-title =
+    .title = Onion site keys
+
+onion-site-saved-keys-dialog-intro = The following onion site keys are stored on your computer.
+onion-site-saved-keys-dialog-table-header-site =
+    .label = Onion site
+# "Key" is a noun, for an access key.
+onion-site-saved-keys-dialog-table-header-key =
+    .label = Key
+onion-site-saved-keys-dialog-remove-button = Remove
+onion-site-saved-keys-dialog-remove-all-button = Remove all
+onion-site-saved-keys-dialog-fetch-keys-error-message = Unable to retrieve keys from Tor
+onion-site-saved-keys-dialog-remove-keys-error-message = Unable to remove key


=====================================
toolkit/modules/TorStrings.sys.mjs
=====================================
@@ -221,71 +221,6 @@ const Loader = {
     };
   },
 
-  /*
-    Tor Onion Services Strings, e.g., for the authentication prompt.
-  */
-  onionServices() {
-    const tsb = new TorPropertyStringBundle(
-      "chrome://torbutton/locale/torbutton.properties",
-      "onionServices."
-    );
-    const getString = tsb.getString.bind(tsb);
-
-    const retval = {
-      learnMore: getString("learnMore", "Learn more"),
-      authPrompt: {
-        description: getString(
-          "authPrompt.description2",
-          "%S is requesting that you authenticate."
-        ),
-        keyPlaceholder: getString(
-          "authPrompt.keyPlaceholder",
-          "Enter your key"
-        ),
-        done: getString("authPrompt.done", "Done"),
-        doneAccessKey: getString("authPrompt.doneAccessKey", "d"),
-        invalidKey: getString("authPrompt.invalidKey", "Invalid key"),
-        failedToSetKey: getString(
-          "authPrompt.failedToSetKey",
-          "Failed to set key"
-        ),
-      },
-      authPreferences: {
-        header: getString(
-          "authPreferences.header",
-          "Onion Services Authentication"
-        ),
-        overview: getString(
-          "authPreferences.overview",
-          "Some onion services require that you identify yourself with a key"
-        ),
-        savedKeys: getString("authPreferences.savedKeys", "Saved Keys"),
-        dialogTitle: getString(
-          "authPreferences.dialogTitle",
-          "Onion Services Keys"
-        ),
-        dialogIntro: getString(
-          "authPreferences.dialogIntro",
-          "Keys for the following onionsites are stored on your computer"
-        ),
-        onionSite: getString("authPreferences.onionSite", "Onionsite"),
-        onionKey: getString("authPreferences.onionKey", "Key"),
-        remove: getString("authPreferences.remove", "Remove"),
-        removeAll: getString("authPreferences.removeAll", "Remove All"),
-        failedToGetKeys: getString(
-          "authPreferences.failedToGetKeys",
-          "Failed to get keys"
-        ),
-        failedToRemoveKey: getString(
-          "authPreferences.failedToRemoveKey",
-          "Failed to remove key"
-        ),
-      },
-    };
-
-    return retval;
-  } /* Tor Onion Services Strings */,
-
   /*
     OnionLocation
   */
@@ -330,13 +265,6 @@ export const TorStrings = {
     return this._torConnect;
   },
 
-  get onionServices() {
-    if (!this._onionServices) {
-      this._onionServices = Loader.onionServices();
-    }
-    return this._onionServices;
-  },
-
   get onionLocation() {
     if (!this._onionLocation) {
       this._onionLocation = Loader.onionLocation();


=====================================
toolkit/torbutton/chrome/locale/en-US/torbutton.dtd deleted
=====================================
@@ -1,8 +0,0 @@
-<!-- Copyright (c) 2022, The Tor Project, Inc.
-   - This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- Onion services strings. Strings are kept here for ease of translation. -->
-<!ENTITY torbutton.onionServices.authPrompt.tooltip "Open onion service client authentication prompt">
-<!ENTITY torbutton.onionServices.authPrompt.persistCheckboxLabel "Remember this key">


=====================================
toolkit/torbutton/chrome/locale/en-US/torbutton.properties
=====================================
@@ -3,35 +3,6 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-# .Onion Page Info prompt.
-pageInfo_OnionEncryptionWithBitsAndProtocol=Connection Encrypted (Onion Service, %1$S, %2$S bit keys, %3$S)
-pageInfo_OnionEncryption=Connection Encrypted (Onion Service)
-
-# Shared between Onion Auth prompt and preferences
-onionServices.learnMore=Learn more
-
-# Onion Services Authentication prompt
-# LOCALIZATION NOTE: %S will be replaced with the .onion address.
-onionServices.authPrompt.description2=%S is requesting that you authenticate.
-onionServices.authPrompt.keyPlaceholder=Enter your private key for this onion service
-onionServices.authPrompt.done=Done
-onionServices.authPrompt.doneAccessKey=d
-onionServices.authPrompt.invalidKey=Please enter a valid key (52 base32 characters or 44 base64 characters)
-onionServices.authPrompt.failedToSetKey=Unable to configure Tor with your key
-
-# Onion Services Authentication preferences
-onionServices.authPreferences.header=Onion Services Authentication
-onionServices.authPreferences.overview=Some onion services require that you identify yourself with a key (a kind of password) before you can access them.
-onionServices.authPreferences.savedKeys=Saved Keys…
-onionServices.authPreferences.dialogTitle=Onion Service Keys
-onionServices.authPreferences.dialogIntro=Keys for the following onionsites are stored on your computer
-onionServices.authPreferences.onionSite=Onionsite
-onionServices.authPreferences.onionKey=Key
-onionServices.authPreferences.remove=Remove
-onionServices.authPreferences.removeAll=Remove All
-onionServices.authPreferences.failedToGetKeys=Unable to retrieve keys from tor
-onionServices.authPreferences.failedToRemoveKey=Unable to remove key
-
 # Profile/startup error messages.
 # LOCALIZATION NOTE: %S is the application name.
 profileProblemTitle=%S Profile Problem


=====================================
tools/torbrowser/l10n/migrations/bug-42212-onion-services.py
=====================================
@@ -0,0 +1,27 @@
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+    dtd_path = "torbutton.dtd"
+    properties_path = "torbutton.properties"
+
+    ctx.add_transforms(
+        "tor-browser.ftl",
+        "tor-browser.ftl",
+        transforms_from(
+            """
+onion-site-authentication-prompt-learn-more = { COPY(path, "onionServices.learnMore") }
+onion-site-authentication-prompt-remember-checkbox =
+    .label = { COPY(dtd_path, "torbutton.onionServices.authPrompt.persistCheckboxLabel") }
+onion-site-authentication-prompt-invalid-key = { COPY(path, "onionServices.authPrompt.invalidKey") }
+onion-site-authentication-prompt-setting-key-failed = { COPY(path, "onionServices.authPrompt.failedToSetKey") }
+onion-site-authentication-preferences-learn-more = { COPY(path, "onionServices.learnMore") }
+onion-site-saved-keys-dialog-table-header-key =
+    .label = { COPY(path, "onionServices.authPreferences.onionKey") }
+onion-site-saved-keys-dialog-remove-button = { COPY(path, "onionServices.authPreferences.remove") }
+onion-site-saved-keys-dialog-remove-keys-error-message = { COPY(path, "onionServices.authPreferences.failedToRemoveKey") }
+""",
+            dtd_path=dtd_path,
+            path=properties_path,
+        ),
+    )



View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/8b57c07889ebe79606fef2b3e2bf61874645db74...7143db9a8c0cdd6b3bc45ba4e9636dc7e6e290a9

-- 
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/8b57c07889ebe79606fef2b3e2bf61874645db74...7143db9a8c0cdd6b3bc45ba4e9636dc7e6e290a9
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/20240820/846a4602/attachment-0001.htm>


More information about the tor-commits mailing list