[tbb-commits] [Git][tpo/applications/tor-browser][tor-browser-115.9.0esr-13.5-1] 5 commits: fixup! Bug 3455: Add DomainIsolator, for isolating circuit by domain.
richard (@richard)
git at gitlab.torproject.org
Mon Mar 25 21:53:44 UTC 2024
richard pushed to branch tor-browser-115.9.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
99d4a1de by Henry Wilkes at 2024-03-25T09:42:29+00:00
fixup! Bug 3455: Add DomainIsolator, for isolating circuit by domain.
Bug 42209: Migrate tor circuit strings to Fluent.
- - - - -
56ce4cc1 by Henry Wilkes at 2024-03-25T09:42:30+00:00
fixup! Bug 41600: Add a tor circuit display panel.
Bug 42209: Migrate tor circuit strings to Fluent.
- - - - -
76f869d5 by Henry Wilkes at 2024-03-25T09:42:31+00:00
fixup! Tor Browser strings
Bug 42209: Migrate tor circuit strings to Fluent.
- - - - -
724dfc5f by Henry Wilkes at 2024-03-25T09:42:31+00:00
fixup! Add TorStrings module for localization
Bug 42209: Migrate tor circuit strings to Fluent.
- - - - -
966b7f30 by Henry Wilkes at 2024-03-25T09:42:32+00:00
fixup! Tor Browser localization migration scripts.
Bug 42209: Migrate tor circuit strings to Fluent.
- - - - -
11 changed files:
- browser/base/content/appmenu-viewcache.inc.xhtml
- browser/base/content/browser-menubar.inc
- browser/base/content/navigator-toolbox.inc.xhtml
- browser/components/torcircuit/content/torCircuitPanel.css
- browser/components/torcircuit/content/torCircuitPanel.inc.xhtml
- browser/components/torcircuit/content/torCircuitPanel.js
- browser/locales/en-US/browser/tor-browser.ftl
- toolkit/torbutton/chrome/locale/en-US/torbutton.dtd
- toolkit/torbutton/chrome/locale/en-US/torbutton.properties
- tools/torbrowser/l10n/migrate.py
- + tools/torbrowser/l10n/migrations/bug-42209-tor-circuit.py
Changes:
=====================================
browser/base/content/appmenu-viewcache.inc.xhtml
=====================================
@@ -62,7 +62,7 @@
<toolbarbutton id="appMenuNewCircuit"
class="subviewbutton"
key="new-circuit-key"
- label="&torbutton.context_menu.new_circuit_sentence_case;"
+ data-l10n-id="appmenuitem-new-tor-circuit"
oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser);"/>
<toolbarseparator/>
<toolbarbutton id="appMenu-bookmarks-button"
=====================================
browser/base/content/browser-menubar.inc
=====================================
@@ -32,9 +32,7 @@
<menuitem id="menu_newIdentity"
key="new-identity-key" data-l10n-id="menu-new-identity"/>
<menuitem id="menu_newCircuit"
- accesskey="&torbutton.context_menu.new_circuit_key;"
- key="new-circuit-key"
- label="&torbutton.context_menu.new_circuit;"
+ key="new-circuit-key" data-l10n-id="menu-new-tor-circuit"
oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser);"/>
<menuseparator/>
<menuitem id="menu_openLocation"
=====================================
browser/base/content/navigator-toolbox.inc.xhtml
=====================================
@@ -198,7 +198,7 @@
role="button"
class="identity-box-button"
align="center"
- tooltiptext="&torbutton.circuit_display.title;"
+ data-l10n-id="tor-circuit-urlbar-button"
hidden="true">
<image id="tor-circuit-button-icon"/>
</box>
@@ -621,9 +621,8 @@
data-l10n-id="toolbar-new-identity"/>
<toolbarbutton id="new-circuit-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
- label="&torbutton.context_menu.new_circuit;"
- oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser);"
- tooltiptext="&torbutton.context_menu.new_circuit;"/>
+ data-l10n-id="toolbar-new-tor-circuit"
+ oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser);"/>
<toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
observes="View:FullScreen"
=====================================
browser/components/torcircuit/content/torCircuitPanel.css
=====================================
@@ -120,6 +120,15 @@
background-repeat: no-repeat;
}
+.tor-circuit-node-item:not([hidden]) {
+ display: flex;
+ align-items: baseline;
+}
+
+.tor-circuit-node-item > * {
+ flex: 0 0 auto;
+}
+
@media (prefers-color-scheme: dark) {
.tor-circuit-node-item {
/* Light Gray 90 */
@@ -146,9 +155,9 @@
.tor-circuit-region-flag {
margin-inline-end: 0.5em;
height: 16px;
- vertical-align: sub;
+ align-self: center;
/* Don't occupy any vertical height. */
- margin-block-start: -16px;
+ margin-block: -8px;
}
.tor-circuit-region-flag.no-region-flag-src {
@@ -158,7 +167,7 @@
.tor-circuit-addresses {
font-size: smaller;
font-family: monospace;
- margin-inline-start: 0.25em;
+ margin-inline-start: 0.75em;
}
/* Footer buttons */
=====================================
browser/components/torcircuit/content/torCircuitPanel.inc.xhtml
=====================================
@@ -19,44 +19,67 @@
<vbox class="panel-header">
<html:h1 id="tor-circuit-heading"></html:h1>
<html:div id="tor-circuit-alias" hidden="hidden">
- <html:img src="chrome://browser/content/tor-circuit-redirect.svg"
- alt="" />
+ <html:img
+ src="chrome://browser/content/tor-circuit-redirect.svg"
+ alt=""
+ />
<html:p id="tor-circuit-alias-label">
- <html:a />
+ <html:a class="tor-circuit-alias-link" data-l10n-name="alias-link" />
</html:p>
</html:div>
</vbox>
<toolbarseparator/>
<vbox id="tor-circuit-panel-body" class="panel-subview-body">
- <html:p id="tor-circuit-node-list-name">&torbutton.circuit_display.title;</html:p>
+ <html:p
+ id="tor-circuit-node-list-name"
+ data-l10n-id="tor-circuit-panel-node-list-introduction"
+ ></html:p>
<html:ol id="tor-circuit-node-list">
- <html:li id="tor-circuit-start-item"
- class="tor-circuit-node-item">
- </html:li>
- <html:li id="tor-circuit-relays-item"
- class="tor-circuit-node-item tor-circuit-relays-item">
- </html:li>
- <html:li id="tor-circuit-end-item"
- class="tor-circuit-node-item">
- </html:li>
+ <html:li
+ id="tor-circuit-start-item"
+ class="tor-circuit-node-item"
+ data-l10n-id="tor-circuit-panel-node-browser"
+ ></html:li>
+ <html:li
+ id="tor-circuit-relays-item"
+ class="tor-circuit-node-item tor-circuit-relays-item"
+ data-l10n-id="tor-circuit-panel-node-onion-relays"
+ ></html:li>
+ <html:li
+ id="tor-circuit-end-item"
+ class="tor-circuit-node-item"
+ ></html:li>
</html:ol>
+ <html:template id="tor-circuit-node-item-template">
+ <html:li class="tor-circuit-node-item">
+ <html:img class="tor-circuit-region-flag" alt="" />
+ <html:span class="tor-circuit-node-name"></html:span>
+ <html:span class="tor-circuit-addresses"></html:span>
+ </html:li>
+ </html:template>
</vbox>
<toolbarseparator/>
<!-- NOTE: To fully benefit from the .subviewbutton styling, we need to use
- a xul:toolbarbutton rather than a html:button.
- By default, a xul:toolbarbutton is not focusable so we need to add
- tabindex. -->
- <toolbarbutton id="tor-circuit-new-circuit"
- class="subviewbutton panel-subview-footer-button tor-circuit-button"
- tabindex="0"
- aria-labelledby="tor-circuit-new-circuit-label"
- aria-describedby="tor-circuit-new-circuit-description">
+ <toolbarbutton
+ id="tor-circuit-new-circuit"
+ class="subviewbutton panel-subview-footer-button tor-circuit-button"
+ tabindex="0"
+ aria-labelledby="tor-circuit-new-circuit-label"
+ aria-describedby="tor-circuit-new-circuit-description"
+ >
<vbox align="start">
- <label id="tor-circuit-new-circuit-label"
- class="toolbarbutton-text"
- value="&torbutton.context_menu.new_circuit_sentence_case;"/>
- <label id="tor-circuit-new-circuit-description"
- class="tor-circuit-button-description"/>
+ <label
+ id="tor-circuit-new-circuit-label"
+ class="toolbarbutton-text"
+ data-l10n-id="tor-circuit-panel-new-button"
+ />
+ <label
+ id="tor-circuit-new-circuit-description"
+ class="tor-circuit-button-description"
+ />
</vbox>
</toolbarbutton>
</vbox>
=====================================
browser/components/torcircuit/content/torCircuitPanel.js
=====================================
@@ -34,6 +34,12 @@ var gTorCircuitPanel = {
* @type {bool}
*/
_isActive: false,
+ /**
+ * The template element for circuit nodes.
+ *
+ * @type {HTMLTemplateElement?}
+ */
+ _nodeItemTemplate: null,
/**
* The topic on which circuit changes are broadcast.
@@ -62,7 +68,6 @@ var gTorCircuitPanel = {
heading: document.getElementById("tor-circuit-heading"),
alias: document.getElementById("tor-circuit-alias"),
aliasLabel: document.getElementById("tor-circuit-alias-label"),
- aliasLink: document.querySelector("#tor-circuit-alias-label a"),
aliasMenu: document.getElementById("tor-circuit-panel-alias-menu"),
list: document.getElementById("tor-circuit-node-list"),
relaysItem: document.getElementById("tor-circuit-relays-item"),
@@ -73,30 +78,24 @@ var gTorCircuitPanel = {
};
this.toolbarButton = document.getElementById("tor-circuit-button");
- // TODO: These strings should be set in the HTML markup with fluent.
-
- // NOTE: There is already whitespace before and after the link from the
- // XHTML markup.
- const [aliasBefore, aliasAfter] = this._getString(
- "torbutton.circuit_display.connected-to-alias",
- // Placeholder is replaced with the same placeholder. This is a bit of a
- // hack since we want the inserted address to be the rich anchor
- // element already in the DOM, rather than a plain address.
- // We won't have to do this with fluent by using data-l10n-name on the
- // anchor element.
- ["%S"]
- ).split("%S");
- this._panelElements.aliasLabel.prepend(aliasBefore);
- this._panelElements.aliasLabel.append(aliasAfter);
-
- this._panelElements.aliasLink.addEventListener("click", event => {
+ // We add listeners for the .tor-circuit-alias-link.
+ // NOTE: We have to add the listeners to the parent element because the
+ // link (with data-l10n-name="alias-link") will be replaced with a new
+ // cloned instance every time the parent gets re-translated.
+ this._panelElements.aliasLabel.addEventListener("click", event => {
+ if (!this._aliasLink.contains(event.target)) {
+ return;
+ }
event.preventDefault();
if (event.button !== 0) {
return;
}
this._openAlias("tab");
});
- this._panelElements.aliasLink.addEventListener("contextmenu", event => {
+ this._panelElements.aliasLabel.addEventListener("contextmenu", event => {
+ if (!this._aliasLink.contains(event.target)) {
+ return;
+ }
event.preventDefault();
this._panelElements.aliasMenu.openPopupAtScreen(
event.screenX,
@@ -119,21 +118,15 @@ var gTorCircuitPanel = {
document
.getElementById("tor-circuit-panel-alias-menu-copy")
.addEventListener("command", () => {
- if (!this._panelElements.aliasLink.href) {
+ const alias = this._aliasLink?.href;
+ if (!alias) {
return;
}
Cc["@mozilla.org/widget/clipboardhelper;1"]
.getService(Ci.nsIClipboardHelper)
- .copyString(this._panelElements.aliasLink.href);
+ .copyString(alias);
});
- document.getElementById("tor-circuit-start-item").textContent =
- this._getString("torbutton.circuit_display.this_browser");
-
- this._panelElements.relaysItem.textContent = this._getString(
- "torbutton.circuit_display.onion-site-relays"
- );
-
// Button is a xul:toolbarbutton, so we use "command" rather than "click".
document
.getElementById("tor-circuit-new-circuit")
@@ -176,6 +169,13 @@ var gTorCircuitPanel = {
this.show();
});
+ this._nodeItemTemplate = document.getElementById(
+ "tor-circuit-node-item-template"
+ );
+ // Prepare the unknown region name for the current locale.
+ // NOTE: We expect this to complete before the first call to _updateBody.
+ this._localeChanged();
+
this._locationListener = {
onLocationChange: (webProgress, request, locationURI, flags) => {
if (
@@ -194,6 +194,7 @@ var gTorCircuitPanel = {
// Get notifications for circuit changes.
Services.obs.addObserver(this, this.TOR_CIRCUIT_TOPIC);
+ Services.obs.addObserver(this, "intl:app-locales-changed");
},
/**
@@ -203,15 +204,21 @@ var gTorCircuitPanel = {
this._isActive = false;
gBrowser.removeProgressListener(this._locationListener);
Services.obs.removeObserver(this, this.TOR_CIRCUIT_TOPIC);
+ Services.obs.removeObserver(this, "intl:app-locales-changed");
},
/**
* Observe circuit changes.
*/
observe(subject, topic, data) {
- if (topic === this.TOR_CIRCUIT_TOPIC) {
- // TODO: Maybe check if we actually need to do something earlier.
- this._updateCurrentBrowser();
+ switch (topic) {
+ case this.TOR_CIRCUIT_TOPIC:
+ // TODO: Maybe check if we actually need to do something earlier.
+ this._updateCurrentBrowser();
+ break;
+ case "intl:app-locales-changed":
+ this._localeChanged();
+ break;
}
},
@@ -231,6 +238,19 @@ var gTorCircuitPanel = {
this.panel.hidePopup();
},
+ /**
+ * Get the current alias link instance.
+ *
+ * Note that this element instance may change whenever its parent element
+ * (#tor-circuit-alias-label) is re-translated. Attributes should be copied to
+ * the new instance.
+ */
+ get _aliasLink() {
+ return this._panelElements.aliasLabel.querySelector(
+ ".tor-circuit-alias-link"
+ );
+ },
+
/**
* Open the onion alias present in the alias link.
*
@@ -238,12 +258,13 @@ var gTorCircuitPanel = {
* window.
*/
_openAlias(where) {
- if (!this._panelElements.aliasLink.href) {
+ const url = this._aliasLink?.href;
+ if (!url) {
return;
}
// We hide the panel before opening the link.
this.hide();
- window.openWebLinkIn(this._panelElements.aliasLink.href, where);
+ window.openWebLinkIn(url, where);
},
/**
@@ -351,11 +372,6 @@ var gTorCircuitPanel = {
this.toolbarButton.hidden = false;
- if (this.panel.state !== "open" && this.panel.state !== "showing") {
- // Don't update the panel content if it is not open or about to open.
- return;
- }
-
this._updateCircuitPanel();
},
@@ -383,35 +399,15 @@ var gTorCircuitPanel = {
return alias;
},
- /**
- * Get a string from the properties bundle.
- *
- * @param {string} name - The string name.
- * @param {string[]} args - The arguments to pass to the string.
- *
- * @returns {string} The string.
- */
- _getString(name, args = []) {
- if (!this._stringBundle) {
- this._stringBundle = Services.strings.createBundle(
- "chrome://torbutton/locale/torbutton.properties"
- );
- }
- try {
- return this._stringBundle.formatStringFromName(name, args);
- } catch {}
- if (!this._fallbackStringBundle) {
- this._fallbackStringBundle = Services.strings.createBundle(
- "resource://torbutton/locale/en-US/torbutton.properties"
- );
- }
- return this._fallbackStringBundle.formatStringFromName(name, args);
- },
-
/**
* Updates the circuit display in the panel to show the current browser data.
*/
_updateCircuitPanel() {
+ if (this.panel.state !== "open" && this.panel.state !== "showing") {
+ // Don't update the panel content if it is not open or about to open.
+ return;
+ }
+
// NOTE: The _currentBrowserData.nodes data may be stale. In particular, the
// circuit may have expired already, or we're still waiting on the new
// circuit.
@@ -426,6 +422,9 @@ var gTorCircuitPanel = {
this.hide();
return;
}
+
+ this._log.debug("Updating circuit panel");
+
let domain = this._currentBrowserData.domain;
const onionAlias = this._getOnionAlias(domain);
@@ -447,24 +446,31 @@ var gTorCircuitPanel = {
* @param {string?} scheme - The scheme in use for the current domain.
*/
_updateHeading(domain, onionAlias, scheme) {
- this._panelElements.heading.textContent = this._getString(
- "torbutton.circuit_display.heading",
+ document.l10n.setAttributes(
+ this._panelElements.heading,
+ "tor-circuit-panel-heading",
// Only shorten the onion domain if it has no alias.
- [TorUIUtils.shortenOnionAddress(domain)]
+ { host: TorUIUtils.shortenOnionAddress(domain) }
);
if (onionAlias) {
- this._panelElements.aliasLink.textContent =
- TorUIUtils.shortenOnionAddress(onionAlias);
if (scheme === "http" || scheme === "https") {
// We assume the same scheme as the current page for the alias, which we
// expect to be either http or https.
// NOTE: The href property is partially presentational so that the link
// location appears on hover.
- this._panelElements.aliasLink.href = `${scheme}://${onionAlias}`;
+ // NOTE: The href attribute should be copied to any new instances of
+ // .tor-circuit-alias-link (with data-l10n-name="alias-link") when the
+ // parent _panelElements.aliasLabel gets re-translated.
+ this._aliasLink.href = `${scheme}://${onionAlias}`;
} else {
- this._panelElements.aliasLink.removeAttribute("href");
+ this._aliasLink.removeAttribute("href");
}
+ document.l10n.setAttributes(
+ this._panelElements.aliasLabel,
+ "tor-circuit-panel-alias",
+ { alias: TorUIUtils.shortenOnionAddress(onionAlias) }
+ );
this._showPanelElement(this._panelElements.alias, true);
} else {
this._showPanelElement(this._panelElements.alias, false);
@@ -485,20 +491,40 @@ var gTorCircuitPanel = {
* @param {string} domain - The domain to show for the last node.
*/
_updateBody(nodes, domain) {
- // Clean up old items.
- // NOTE: We do not expect focus within a removed node.
- for (const nodeItem of this._nodeItems) {
- nodeItem.remove();
- }
+ // NOTE: Rather than re-creating the <li> nodes from scratch, we prefer
+ // updating existing <li> nodes so that the display does not "flicker" in
+ // width as we wait for Fluent DOM to fill the nodes with text content. I.e.
+ // the existing node and text will remain in place, occupying the same
+ // width, up until it is replaced by Fluent DOM.
+ for (let index = 0; index < nodes.length; index++) {
+ if (index >= this._nodeItems.length) {
+ const newItem =
+ this._nodeItemTemplate.content.children[0].cloneNode(true);
+ const flagEl = newItem.querySelector(".tor-circuit-region-flag");
+ // Hide region flag whenever the flag src does not exist.
+ flagEl.addEventListener("error", () => {
+ flagEl.classList.add("no-region-flag-src");
+ flagEl.removeAttribute("src");
+ });
+ this._panelElements.list.insertBefore(
+ newItem,
+ this._panelElements.relaysItem
+ );
- this._nodeItems = nodes.map((nodeData, index) => {
- const nodeItem = this._createCircuitNodeItem(nodeData, index === 0);
- this._panelElements.list.insertBefore(
- nodeItem,
- this._panelElements.relaysItem
+ this._nodeItems.push(newItem);
+ }
+ this._updateCircuitNodeItem(
+ this._nodeItems[index],
+ nodes[index],
+ index === 0
);
- return nodeItem;
- });
+ }
+
+ // Remove excess items.
+ // NOTE: We do not expect focus within a removed node.
+ while (nodes.length < this._nodeItems.length) {
+ this._nodeItems.pop().remove();
+ }
this._showPanelElement(
this._panelElements.relaysItem,
@@ -511,40 +537,49 @@ var gTorCircuitPanel = {
// Button description text, depending on whether our first node was a
// bridge, or otherwise a guard.
- this._panelElements.newCircuitDescription.value = this._getString(
+ document.l10n.setAttributes(
+ this._panelElements.newCircuitDescription,
nodes[0].bridgeType === null
- ? "torbutton.circuit_display.new-circuit-guard-description"
- : "torbutton.circuit_display.new-circuit-bridge-description"
+ ? "tor-circuit-panel-new-button-description-guard"
+ : "tor-circuit-panel-new-button-description-bridge"
);
},
/**
- * Create a node item for the given circuit node data.
+ * Update a node item for the given circuit node data.
*
+ * @param {Element} nodeItem - The item to update.
* @param {NodeData} node - The circuit node data to create an item for.
* @param {bool} isCircuitStart - Whether this is the first node in the
* circuit.
*/
- _createCircuitNodeItem(node, isCircuitStart) {
- let nodeName;
- // We do not show a flag for bridge nodes.
- let regionCode = null;
+ _updateCircuitNodeItem(nodeItem, node, isCircuitStart) {
+ const nameEl = nodeItem.querySelector(".tor-circuit-node-name");
+ let flagSrc = null;
+
if (node.bridgeType === null) {
- regionCode = node.regionCode;
- if (!regionCode) {
- nodeName = this._getString("torbutton.circuit_display.unknown_region");
- } else {
- nodeName = Services.intl.getRegionDisplayNames(undefined, [
- regionCode,
- ])[0];
- }
+ const regionCode = node.regionCode;
+ flagSrc = this._regionFlagSrc(regionCode);
+
+ const regionName = regionCode
+ ? Services.intl.getRegionDisplayNames(undefined, [regionCode])[0]
+ : this._unknownRegionName;
+
if (isCircuitStart) {
- nodeName = this._getString(
- "torbutton.circuit_display.region-guard-node",
- [nodeName]
+ document.l10n.setAttributes(
+ nameEl,
+ "tor-circuit-panel-node-region-guard",
+ { region: regionName }
);
+ } else {
+ // Set the text content directly, rather than using Fluent.
+ nameEl.removeAttribute("data-l10n-id");
+ nameEl.removeAttribute("data-l10n-args");
+ nameEl.textContent = regionName;
}
} else {
+ // Do not show a flag for bridges.
+
let bridgeType = node.bridgeType;
if (bridgeType === "meek_lite") {
bridgeType = "meek";
@@ -552,55 +587,72 @@ var gTorCircuitPanel = {
bridgeType = "";
}
if (bridgeType) {
- nodeName = this._getString(
- "torbutton.circuit_display.tor_typed_bridge",
- [bridgeType]
+ document.l10n.setAttributes(
+ nameEl,
+ "tor-circuit-panel-node-typed-bridge",
+ { "bridge-type": bridgeType }
);
} else {
- nodeName = this._getString("torbutton.circuit_display.tor_bridge");
+ document.l10n.setAttributes(nameEl, "tor-circuit-panel-node-bridge");
}
}
- const nodeItem = document.createElement("li");
- nodeItem.classList.add("tor-circuit-node-item");
-
- const regionFlagEl = this._regionFlag(regionCode);
- if (regionFlagEl) {
- nodeItem.append(regionFlagEl);
+ const flagEl = nodeItem.querySelector(".tor-circuit-region-flag");
+ flagEl.classList.toggle("no-region-flag-src", !flagSrc);
+ if (flagSrc) {
+ flagEl.setAttribute("src", flagSrc);
+ } else {
+ flagEl.removeAttribute("src");
}
- // Add whitespace after name for the addresses.
- nodeItem.append(nodeName + " ");
-
- if (node.ipAddrs) {
- const addressesEl = document.createElement("span");
- addressesEl.classList.add("tor-circuit-addresses");
- let firstAddr = true;
- for (const ip of node.ipAddrs) {
- if (firstAddr) {
- firstAddr = false;
- } else {
- addressesEl.append(", ");
- }
- // We use a <code> element to give screen readers a hint that
- // punctuation is different for IP addresses.
- const ipEl = document.createElement("code");
- // TODO: Current HTML-aam 1.0 specs map the <code> element to the "code"
- // role.
- // However, mozilla-central commented out this mapping in
- // accessible/base/HTMLMarkupMap.h because the HTML-aam specs at the
- // time did not do this.
- // See hg.mozilla.org/mozilla-central/rev/51eebe7d6199#l2.12
- // For now we explicitly add the role="code", but once this is fixed
- // from mozilla-central we should remove this.
- ipEl.setAttribute("role", "code");
- ipEl.classList.add("tor-circuit-ip-address");
- ipEl.textContent = ip;
- addressesEl.append(ipEl);
+ const addressesEl = nodeItem.querySelector(".tor-circuit-addresses");
+ // Empty children.
+ addressesEl.replaceChildren();
+ let firstAddr = true;
+ for (const ip of node.ipAddrs) {
+ if (firstAddr) {
+ firstAddr = false;
+ } else {
+ addressesEl.append(", ");
}
- nodeItem.append(addressesEl);
+ const ipEl = document.createElement("code");
+ // TODO: Current HTML-aam 1.0 specs map the <code> element to the "code"
+ // role.
+ // However, mozilla-central commented out this mapping in
+ // accessible/base/HTMLMarkupMap.h because the HTML-aam specs at the
+ // time did not do this.
+ // See hg.mozilla.org/mozilla-central/rev/51eebe7d6199#l2.12
+ //
+ // This was updated in mozilla bug 1834931, for ESR 128
+ //
+ // For now we explicitly add the role="code", but once this is fixed
+ // from mozilla-central we should remove this.
+ ipEl.setAttribute("role", "code");
+ ipEl.classList.add("tor-circuit-ip-address");
+ ipEl.textContent = ip;
+ addressesEl.append(ipEl);
}
+ },
+
+ /**
+ * The string to use for unknown region names.
+ *
+ * Will be updated to match the current locale.
+ *
+ * @type {string}
+ */
+ _unknownRegionName: "Unknown region",
- return nodeItem;
+ /**
+ * Update the name for regions to match the current locale.
+ */
+ _localeChanged() {
+ document.l10n
+ .formatValue("tor-circuit-panel-node-unknown-region")
+ .then(name => {
+ this._unknownRegionName = name;
+ // Update the panel for the new region names, if it is shown.
+ this._updateCircuitPanel();
+ });
},
/**
@@ -609,9 +661,9 @@ var gTorCircuitPanel = {
* @param {string?} regionCode - The code to convert. It should be an upper
* case 2-letter BCP47 Region subtag to be converted into a flag.
*
- * @returns {HTMLImgElement?} The emoji flag img, or null if there is no flag.
+ * @returns {src?} The emoji flag img src, or null if there is no flag.
*/
- _regionFlag(regionCode) {
+ _regionFlagSrc(regionCode) {
if (!regionCode?.match(/^[A-Z]{2}$/)) {
return null;
}
@@ -624,20 +676,7 @@ var gTorCircuitPanel = {
.map(cp => cp.toString(16))
.join("-");
- const flagEl = document.createElement("img");
- // Decorative.
- flagEl.alt = "";
- flagEl.classList.add("tor-circuit-region-flag");
- // Remove self if there is no matching flag found.
- flagEl.addEventListener(
- "error",
- () => {
- flagEl.classList.add("no-region-flag-src");
- },
- { once: true }
- );
- flagEl.src = `chrome://browser/content/tor-circuit-flags/${flagName}.svg`;
- return flagEl;
+ return `chrome://browser/content/tor-circuit-flags/${flagName}.svg`;
},
/**
=====================================
browser/locales/en-US/browser/tor-browser.ftl
=====================================
@@ -326,3 +326,63 @@ about-dialog-browser-license-link = Licensing Information
# "Tor" and "The Onion Logo" are trademark names, so should not be translated (not including the quote marks, which can be localized).
# "The Tor Project, Inc." is an organisation name.
about-dialog-trademark-statement = “Tor” and “The Onion Logo” are registered trademarks of The Tor Project, Inc.
+
+## New tor circuit.
+
+# Shown in the File menu.
+# Uses title case for English (US).
+menu-new-tor-circuit =
+ .label = New Tor Circuit for this Site
+ .accesskey = C
+
+# Shown in the application menu (hamburger menu).
+# Uses sentence case for English (US).
+appmenuitem-new-tor-circuit =
+ .label = New Tor circuit for this site
+
+# Toolbar button to trigger a new circuit, available through toolbar customization.
+# Uses sentence case for English (US).
+# ".label" is the accessible name, and is visible in the overflow menu and when
+# customizing the toolbar.
+# ".tooltiptext" will be identical to the label.
+toolbar-new-tor-circuit =
+ .label = New Tor circuit for this site
+ .tooltiptext = { toolbar-new-tor-circuit.label }
+
+## Tor circuit URL bar button.
+
+# The tooltip also acts as the accessible name.
+tor-circuit-urlbar-button =
+ .tooltiptext = Tor Circuit
+
+## Tor circuit panel.
+
+# $host (String) - The host name shown in the URL bar, potentially shortened.
+tor-circuit-panel-heading = Circuit for { $host }
+# Shown when the current address is a ".tor.onion" alias.
+# $alias (String) - The alias onion address. This should be wrapped in '<a data-l10n-name="alias-link">' and '</a>', which will link to the corresponding address.
+tor-circuit-panel-alias = Connected to <a data-l10n-name="alias-link">{ $alias }</a>
+
+# Text just before the list of circuit nodes.
+tor-circuit-panel-node-list-introduction = Tor Circuit
+# First node in the list of circuit nodes. Refers to Tor Browser.
+tor-circuit-panel-node-browser = This browser
+# Represents a number of unknown relays that complete a connection to an ".onion" site.
+tor-circuit-panel-node-onion-relays = Onion site relays
+# Represents the bridge node used to connect to the Tor network.
+# $bridge-type (String) - The name for the type of bridge used: meek, obfs4, snowflake, etc.
+tor-circuit-panel-node-typed-bridge = Bridge: { $bridge-type }
+# Represents the bridge node used to connect to the Tor network when the bridge type is unknown.
+tor-circuit-panel-node-bridge = Bridge
+# Represents the initial guard node used for a tor circuit.
+# $region (String) - The region name for the guard node, already localized.
+tor-circuit-panel-node-region-guard = { $region } (guard)
+# Represents a circuit node with an unknown regional location.
+tor-circuit-panel-node-unknown-region = Unknown region
+
+# Uses sentence case for English (US).
+tor-circuit-panel-new-button = New Tor circuit for this site
+# Shown when the first node in the circuit is a guard node, rather than a bridge.
+tor-circuit-panel-new-button-description-guard = Your guard node may not change
+# Shown when the first node in the circuit is a bridge node.
+tor-circuit-panel-new-button-description-bridge = Your bridge may not change
=====================================
toolkit/torbutton/chrome/locale/en-US/torbutton.dtd
=====================================
@@ -3,12 +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/. -->
-<!ENTITY torbutton.context_menu.new_circuit "New Tor Circuit for this Site">
-<!ENTITY torbutton.context_menu.new_circuit_sentence_case "New Tor circuit for this site">
-<!ENTITY torbutton.context_menu.new_circuit_key "C">
-
-<!ENTITY torbutton.circuit_display.title "Tor Circuit">
-
<!-- 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,25 +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/.
-# Circuit display
-# LOCALIZATION NOTE: %S will be the host name shown in the URL bar.
-torbutton.circuit_display.heading = Circuit for %S
-# LOCALIZATION NOTE: %S will be the alias onion address.
-torbutton.circuit_display.connected-to-alias = Connected to %S
-torbutton.circuit_display.this_browser = This browser
-torbutton.circuit_display.onion-site-relays = Onion site relays
-# LOCALIZATION NOTE: %S will be the bridge type name (meek, obfs4, snowflake,
-# etc).
-torbutton.circuit_display.tor_typed_bridge = Bridge: %S
-# LOCALIZATION NOTE: Used when the bridge type is unknown.
-torbutton.circuit_display.tor_bridge = Bridge
-# LOCALIZATION NOTE: Used when a circuit node's regional location is unknown.
-torbutton.circuit_display.unknown_region = Unknown region
-# LOCALIZATION NOTE: %S will be the localized region name for the guard node.
-torbutton.circuit_display.region-guard-node = %S (guard)
-torbutton.circuit_display.new-circuit-guard-description = Your guard node may not change
-torbutton.circuit_display.new-circuit-bridge-description = Your bridge may not change
-
# Download pane warning
torbutton.download.warning.title = Be careful opening downloads
# %S will be a link to the Tails operating system website. With the content given by torbutton.download.warning.tails_brand_name
=====================================
tools/torbrowser/l10n/migrate.py
=====================================
@@ -253,16 +253,13 @@ class TorBrowserMigrationContext(MigrationContext):
if path not in self.localization_resources
)
- def tb_get_transformed(self, target_path, transform_id):
+ def tb_get_transform(self, target_path, transform_id):
"""
Find the transformation node with the given id for the given path.
-
- The node will be evaluated (converted to regular fluent.ast) before it
- is returned.
"""
for node in self.transforms[target_path]:
if node.id.name == transform_id:
- return self.evaluate(node)
+ return node
return None
def tb_get_reference_entry(self, target_path, entry_id):
@@ -330,6 +327,21 @@ class TorBrowserMigrator:
ctx = self._get_migration_context(locale, locale_dir)
+ # NOTE: We do not use the existing ctx.serialize_changeset method.
+ # The problem with this approach was that it would re-shuffle the order
+ # of already existing strings to match the en-US locale.
+ # But Weblate currently does not preserve the order of translated
+ # strings: https://github.com/WeblateOrg/weblate/issues/11134
+ # so this created extra noise in the diff.
+ # Instead, we just always append transformations to the end of the
+ # existing file.
+ # Moreover, it would inject group comments into the translated files,
+ # which Weblate does not handle well. Instead, we just do not add any
+ # comments.
+ #
+ # In case we want to use it again in the future, here is a reference
+ # to how it works:
+ #
# ctx.serialize_changeset expects a set of (path, identifier) of
# localization resources that can be used to evaluate the
# transformations.
@@ -344,76 +356,115 @@ class TorBrowserMigrator:
# one step, so we want to fill the changeset with all required
# (path, identifier) pairs found in the localization resources.
- # Choose the transforms that are required and available.
- changeset = set()
available_strings = ctx.tb_get_available_strings()
- for (target_path, transform_id), dep_set in ctx.dependencies.items():
- # ctx.dependencies is a dict of dependencies for all
- # transformations
- # { (target_path, transform_identifier): set(
- # (localization_path, string_identifier),
- # )}
- #
- # e.g. if we want to create a new fluent Message called
- # "new-string1", and it uses "oldString1" from "old-file1.dtd"
- # and "oldString2" from "old-file2.dtd". And "new-string2" using
- # "oldString3" from "old-file2.dtd", it would be
- # {
- # ("new-file.ftl", "new-string1"): set(
- # ("old-file1.dtd", "oldString1"),
- # ("old-file2.dtd", "oldString2"),
- # ),
- # ("new-file.ftl", "new-string2"): set(
- # ("old-file2.dtd", "oldString3"),
- # ),
- # }
- can_transform = True
- for dep in dep_set:
- path, string_id = dep
- if dep not in available_strings:
- can_transform = False
- self.logger.info(
- f"Skipping transform {target_path}:{transform_id} for "
- f"'{locale}' locale because it is missing the "
- f"string {path}:{string_id}."
- )
- break
- # Strings in legacy formats might have an entry in the file
- # that is just a copy of the en-US strings.
- # For these we want to check the weblate metadata to ensure
- # it is a translated string.
- if not path.endswith(
- ".ftl"
- ) and not self.weblate_metadata.is_translated(
- os.path.join("en-US", path),
- os.path.join(locale, path),
- string_id,
- ):
- can_transform = False
+ wrote_file = False
+ errors = []
+
+ for target_path, reference in ctx.reference_resources.items():
+ translated_ids = [
+ entry.id.name
+ for entry in ctx.target_resources[target_path].body
+ if isinstance(entry, (ast.Message, ast.Term))
+ # NOTE: We're assuming that the Message and Term ids do not
+ # conflict with each other.
+ ]
+ new_entries = []
+
+ # Apply transfomations in the order they appear in the reference
+ # (en-US) file.
+ for entry in reference.body:
+ if not isinstance(entry, (ast.Message, ast.Term)):
+ continue
+ transform_id = entry.id.name
+ transform = ctx.tb_get_transform(target_path, transform_id)
+ if not transform:
+ # No transformation for this reference entry.
+ continue
+
+ if transform_id in translated_ids:
self.logger.info(
- f"Skipping transform {target_path}:{transform_id} for "
- f"'{locale}' locale because the string "
- f"{path}:{string_id} has not been translated on "
- "weblate."
+ f"Skipping transform {target_path}:{transform_id} "
+ f"for '{locale}' locale because it already has a "
+ f"translation."
)
- break
- if can_transform:
- changeset.update(dep_set)
+ continue
- print("", file=sys.stderr)
- wrote_file = False
- errors = []
- for path, fluent in ctx.serialize_changeset(changeset).items():
- full_path = os.path.join(locale_dir, path)
+ # ctx.dependencies is a dict of dependencies for all
+ # transformations
+ # { (target_path, transform_identifier): set(
+ # (localization_path, string_identifier),
+ # )}
+ #
+ # e.g. if we want to create a new fluent Message called
+ # "new-string1", and it uses "oldString1" from "old-file1.dtd"
+ # and "oldString2" from "old-file2.dtd". And "new-string2" using
+ # "oldString3" from "old-file2.dtd", it would be
+ # {
+ # ("new-file.ftl", "new-string1"): set(
+ # ("old-file1.dtd", "oldString1"),
+ # ("old-file2.dtd", "oldString2"),
+ # ),
+ # ("new-file.ftl", "new-string2"): set(
+ # ("old-file2.dtd", "oldString3"),
+ # ),
+ # }
+ dep_set = ctx.dependencies[(target_path, transform_id)]
+ can_transform = True
+ for dep in dep_set:
+ path, string_id = dep
+ if dep not in available_strings:
+ can_transform = False
+ self.logger.info(
+ f"Skipping transform {target_path}:{transform_id} "
+ f"for '{locale}' locale because it is missing the "
+ f"string {path}:{string_id}."
+ )
+ break
+ # Strings in legacy formats might have an entry in the file
+ # that is just a copy of the en-US strings.
+ # For these we want to check the weblate metadata to ensure
+ # it is a translated string.
+ if not path.endswith(
+ ".ftl"
+ ) and not self.weblate_metadata.is_translated(
+ os.path.join("en-US", path),
+ os.path.join(locale, path),
+ string_id,
+ ):
+ can_transform = False
+ self.logger.info(
+ f"Skipping transform {target_path}:{transform_id} "
+ f"for '{locale}' locale because the string "
+ f"{path}:{string_id} has not been translated on "
+ "weblate."
+ )
+ break
+ if not can_transform:
+ continue
+
+ # Run the transformation.
+ new_entries.append(ctx.evaluate(transform))
+
+ if not new_entries:
+ continue
+
+ full_path = os.path.join(locale_dir, target_path)
+ print("", file=sys.stderr)
self.logger.info(f"Writing to {full_path}")
- with open(full_path, "w") as file:
- file.write(fluent)
- wrote_file = True
+ # For Fluent we can just serialize the transformations and append
+ # them to the end of the existing file.
+ resource = ast.Resource(new_entries)
+ with open(full_path, "a") as file:
+ file.write(serialize(resource))
+
+ with open(full_path, "r") as file:
+ full_content = file.read()
+ wrote_file = True
# Collect any fluent parsing errors from the newly written file.
errors.extend(
(full_path, message, line, sample)
- for message, line, sample in self._fluent_errors(fluent)
+ for message, line, sample in self._fluent_errors(full_content)
)
if not wrote_file:
@@ -547,7 +598,7 @@ class TorBrowserMigrator:
have_error = True
continue
- transformed = ctx.tb_get_transformed(target_path, transform_id)
+ transformed = ctx.evaluate(ctx.tb_get_transform(target_path, transform_id))
reference_entry = ctx.tb_get_reference_entry(target_path, transform_id)
if reference_entry is None:
self.logger.error(
=====================================
tools/torbrowser/l10n/migrations/bug-42209-tor-circuit.py
=====================================
@@ -0,0 +1,83 @@
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import VARIABLE_REFERENCE, transforms_from
+from fluent.migrate.transforms import CONCAT, REPLACE
+
+
+def migrate(ctx):
+ legacy_dtd = "torbutton.dtd"
+ legacy_properties = "torbutton.properties"
+ ctx.add_transforms(
+ "tor-browser.ftl",
+ "tor-browser.ftl",
+ transforms_from(
+ """
+menu-new-tor-circuit =
+ .label = { COPY(dtd_path, "torbutton.context_menu.new_circuit") }
+ .accesskey = { COPY(dtd_path, "torbutton.context_menu.new_circuit_key") }
+appmenuitem-new-tor-circuit =
+ .label = { COPY(dtd_path, "torbutton.context_menu.new_circuit_sentence_case") }
+toolbar-new-tor-circuit =
+ .label = { COPY(dtd_path, "torbutton.context_menu.new_circuit_sentence_case") }
+ .tooltiptext = { toolbar-new-tor-circuit.label }
+
+tor-circuit-urlbar-button =
+ .tooltiptext = { COPY(dtd_path, "torbutton.circuit_display.title") }
+
+tor-circuit-panel-node-list-introduction = { COPY(dtd_path, "torbutton.circuit_display.title") }
+tor-circuit-panel-node-browser = { COPY(path, "torbutton.circuit_display.this_browser") }
+tor-circuit-panel-node-onion-relays = { COPY(path, "torbutton.circuit_display.onion-site-relays") }
+tor-circuit-panel-node-bridge = { COPY(path, "torbutton.circuit_display.tor_bridge") }
+tor-circuit-panel-node-unknown-region = { COPY(path, "torbutton.circuit_display.unknown_region") }
+
+tor-circuit-panel-new-button = { COPY(dtd_path, "torbutton.context_menu.new_circuit_sentence_case") }
+tor-circuit-panel-new-button-description-guard = { COPY(path, "torbutton.circuit_display.new-circuit-guard-description") }
+tor-circuit-panel-new-button-description-bridge = { COPY(path, "torbutton.circuit_display.new-circuit-bridge-description") }
+""",
+ dtd_path=legacy_dtd,
+ path=legacy_properties,
+ )
+ + [
+ # Replace "%S" with "{ $host }"
+ FTL.Message(
+ id=FTL.Identifier("tor-circuit-panel-heading"),
+ value=REPLACE(
+ legacy_properties,
+ "torbutton.circuit_display.heading",
+ {"%1$S": VARIABLE_REFERENCE("host")},
+ ),
+ ),
+ # Replace "%S" with "<a data-l10n-name="alias-link">{ $alias }</a>"
+ FTL.Message(
+ id=FTL.Identifier("tor-circuit-panel-alias"),
+ value=REPLACE(
+ legacy_properties,
+ "torbutton.circuit_display.connected-to-alias",
+ {
+ "%1$S": CONCAT(
+ FTL.TextElement('<a data-l10n-name="alias-link">'),
+ VARIABLE_REFERENCE("alias"),
+ FTL.TextElement("</a>"),
+ )
+ },
+ ),
+ ),
+ # Replace "%S" with "{ $region }"
+ FTL.Message(
+ id=FTL.Identifier("tor-circuit-panel-node-region-guard"),
+ value=REPLACE(
+ legacy_properties,
+ "torbutton.circuit_display.region-guard-node",
+ {"%1$S": VARIABLE_REFERENCE("region")},
+ ),
+ ),
+ # Replace "%S" with "{ $bridge-type }"
+ FTL.Message(
+ id=FTL.Identifier("tor-circuit-panel-node-typed-bridge"),
+ value=REPLACE(
+ legacy_properties,
+ "torbutton.circuit_display.tor_typed_bridge",
+ {"%1$S": VARIABLE_REFERENCE("bridge-type")},
+ ),
+ ),
+ ],
+ )
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/b7fc915f3ed100830bd8574a62f9cd653c1ec250...966b7f3067cc7b71b56178af98f848451f9a7955
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/b7fc915f3ed100830bd8574a62f9cd653c1ec250...966b7f3067cc7b71b56178af98f848451f9a7955
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/tbb-commits/attachments/20240325/3c60b8ae/attachment-0001.htm>
More information about the tbb-commits
mailing list