lists.torproject.org
Sign In Sign Up
Manage this list Sign In Sign Up

Keyboard Shortcuts

Thread View

  • j: Next unread message
  • k: Previous unread message
  • j a: Jump to all threads
  • j l: Jump to MailingList overview

tbb-commits

Thread Start a new thread
Threads by month
  • ----- 2025 -----
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2024 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2023 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2022 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2021 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2020 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2019 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2018 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2017 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2016 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2015 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2014 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • June
  • May
  • April
  • March
  • February
tbb-commits@lists.torproject.org

October 2025

  • 1 participants
  • 140 discussions
[Git][tpo/applications/mullvad-browser] Pushed new tag mullvad-browser-140.4.0esr-15.0-1-build2
by Pier Angelo Vendrame (@pierov) 16 Oct '25

16 Oct '25
Pier Angelo Vendrame pushed new tag mullvad-browser-140.4.0esr-15.0-1-build2 at The Tor Project / Applications / Mullvad Browser -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/tree/mullv… You're receiving this email because of your account on gitlab.torproject.org.
1 0
0 0
[Git][tpo/applications/tor-browser] Pushed new tag tor-browser-140.4.0esr-15.0-1-build2
by Pier Angelo Vendrame (@pierov) 16 Oct '25

16 Oct '25
Pier Angelo Vendrame pushed new tag tor-browser-140.4.0esr-15.0-1-build2 at The Tor Project / Applications / Tor Browser -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/tree/tor-brows… You're receiving this email because of your account on gitlab.torproject.org.
1 0
0 0
[Git][tpo/applications/mullvad-browser][mullvad-browser-140.4.0esr-15.0-1] fixup! Firefox preference overrides.
by morgan (@morgan) 16 Oct '25

16 Oct '25
morgan pushed to branch mullvad-browser-140.4.0esr-15.0-1 at The Tor Project / Applications / Mullvad Browser Commits: e64d0d0c by hackademix at 2025-10-15T18:07:33+00:00 fixup! Firefox preference overrides. BB 44262: Disable adding search engines from HTML forms - - - - - 1 changed file: - browser/app/profile/001-base-profile.js Changes: ===================================== browser/app/profile/001-base-profile.js ===================================== @@ -216,6 +216,9 @@ pref("browser.safebrowsing.provider.mozilla.gethashURL", ""); pref("browser.urlbar.recentsearches.featureGate", false); pref("browser.urlbar.suggest.recentsearches", false); +// tor-browser#44262: Disable adding search engines from HTML forms +pref("browser.urlbar.update2.engineAliasRefresh", false); + // Disable the UITour API // See tor-browser#41457 and // https://bugzilla.mozilla.org/show_bug.cgi?id=1915280 View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/e64… -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/e64… You're receiving this email because of your account on gitlab.torproject.org.
1 0
0 0
[Git][tpo/applications/tor-browser][base-browser-140.4.0esr-15.0-1] fixup! Firefox preference overrides.
by morgan (@morgan) 16 Oct '25

16 Oct '25
morgan pushed to branch base-browser-140.4.0esr-15.0-1 at The Tor Project / Applications / Tor Browser Commits: 85dc5eb3 by hackademix at 2025-10-15T18:07:11+00:00 fixup! Firefox preference overrides. BB 44262: Disable adding search engines from HTML forms - - - - - 1 changed file: - browser/app/profile/001-base-profile.js Changes: ===================================== browser/app/profile/001-base-profile.js ===================================== @@ -216,6 +216,9 @@ pref("browser.safebrowsing.provider.mozilla.gethashURL", ""); pref("browser.urlbar.recentsearches.featureGate", false); pref("browser.urlbar.suggest.recentsearches", false); +// tor-browser#44262: Disable adding search engines from HTML forms +pref("browser.urlbar.update2.engineAliasRefresh", false); + // Disable the UITour API // See tor-browser#41457 and // https://bugzilla.mozilla.org/show_bug.cgi?id=1915280 View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/85dc5eb… -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/85dc5eb… You're receiving this email because of your account on gitlab.torproject.org.
1 0
0 0
[Git][tpo/applications/tor-browser][tor-browser-140.4.0esr-15.0-1] fixup! Firefox preference overrides.
by morgan (@morgan) 16 Oct '25

16 Oct '25
morgan pushed to branch tor-browser-140.4.0esr-15.0-1 at The Tor Project / Applications / Tor Browser Commits: 2d9ad814 by hackademix at 2025-10-15T18:02:31+00:00 fixup! Firefox preference overrides. BB 44262: Disable adding search engines from HTML forms - - - - - 1 changed file: - browser/app/profile/001-base-profile.js Changes: ===================================== browser/app/profile/001-base-profile.js ===================================== @@ -216,6 +216,9 @@ pref("browser.safebrowsing.provider.mozilla.gethashURL", ""); pref("browser.urlbar.recentsearches.featureGate", false); pref("browser.urlbar.suggest.recentsearches", false); +// tor-browser#44262: Disable adding search engines from HTML forms +pref("browser.urlbar.update2.engineAliasRefresh", false); + // Disable the UITour API // See tor-browser#41457 and // https://bugzilla.mozilla.org/show_bug.cgi?id=1915280 View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/2d9ad81… -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/2d9ad81… You're receiving this email because of your account on gitlab.torproject.org.
1 0
0 0
[Git][tpo/applications/tor-browser][tor-browser-140.4.0esr-15.0-1] fixup! TB 27476: Implement about:torconnect captive portal within Tor Browser
by morgan (@morgan) 15 Oct '25

15 Oct '25
morgan pushed to branch tor-browser-140.4.0esr-15.0-1 at The Tor Project / Applications / Tor Browser Commits: 23c8a66b by Henry Wilkes at 2025-10-15T17:43:21+00:00 fixup! TB 27476: Implement about:torconnect captive portal within Tor Browser TB 44101: Show tor connection status in the nav bar when the tab bar is hidden. - - - - - 4 changed files: - browser/base/content/navigator-toolbox.inc.xhtml - toolkit/components/torconnect/content/torConnectTitlebarStatus.css - + toolkit/components/torconnect/content/torConnectTitlebarStatus.inc.xhtml - toolkit/components/torconnect/content/torConnectTitlebarStatus.js Changes: ===================================== browser/base/content/navigator-toolbox.inc.xhtml ===================================== @@ -100,12 +100,7 @@ #include private-browsing-indicator.inc.xhtml <toolbarbutton class="content-analysis-indicator toolbarbutton-1 content-analysis-indicator-icon"/> - <html:div id="tor-connect-titlebar-status" role="status"> - <html:img alt="" - src="chrome://global/content/torconnect/tor-not-connected-to-connected-animated.svg" /> - <html:span id="tor-connect-titlebar-status-label"></html:span> - </html:div> - +#include ../../../toolkit/components/torconnect/content/torConnectTitlebarStatus.inc.xhtml #include titlebar-items.inc.xhtml </toolbar> @@ -526,6 +521,7 @@ <hbox class="titlebar-spacer" type="post-tabs"/> #include private-browsing-indicator.inc.xhtml <toolbarbutton class="content-analysis-indicator toolbarbutton-1 content-analysis-indicator-icon"/> +#include ../../../toolkit/components/torconnect/content/torConnectTitlebarStatus.inc.xhtml #include titlebar-items.inc.xhtml </toolbar> ===================================== toolkit/components/torconnect/content/torConnectTitlebarStatus.css ===================================== @@ -1,16 +1,28 @@ -#tor-connect-titlebar-status:not([hidden]) { +.tor-connect-titlebar-status:not([hidden]) { display: flex; align-items: center; - /* Want same as #private-browsing-indicator-with-label */ + /* Want same as .private-browsing-indicator-with-label */ margin-inline: 7px; + + #navigator-toolbox[tabs-hidden] #TabsToolbar > & { + /* Hide in the tabs bar when the tabs bar is hidden. E.g. when using + * vertical tabs. Should be shown in the #nav-bar instead. + * See tor-browser#44101. */ + display: none; + } + + #navigator-toolbox:not([tabs-hidden]) #nav-bar > & { + /* Hide in the nav bar when the (horizontal) tabs bar is visible. */ + display: none; + } } -#tor-connect-titlebar-status-label { +.tor-connect-titlebar-status-label { margin-inline: 6px; white-space: nowrap; } -#tor-connect-titlebar-status img { +.tor-connect-titlebar-status img { -moz-context-properties: fill, stroke; fill: var(--icon-color); stroke: var(--icon-color); @@ -25,7 +37,7 @@ object-position: var(--tor-not-connected-offset); } -#tor-connect-titlebar-status.tor-connect-status-potentially-blocked img { +.tor-connect-titlebar-status.tor-connect-status-potentially-blocked img { /* NOTE: context-stroke is only used for the first "frame" for the slash. When * we assign the potentially-blocked class, we do *not* expect to be connected * at the same time, so we only expect this first frame to be visible in this @@ -33,17 +45,17 @@ stroke: var(--icon-color-critical); } -#tor-connect-titlebar-status.tor-connect-status-connected img { +.tor-connect-titlebar-status.tor-connect-status-connected img { object-position: var(--tor-connected-offset); } @media not ((prefers-contrast) or (forced-colors)) { /* Make the connected text and icon purple. */ - #tor-connect-titlebar-status.tor-connect-status-connected { + .tor-connect-titlebar-status.tor-connect-status-connected { color: var(--tor-text-color); } - #tor-connect-titlebar-status.tor-connect-status-connected img { + .tor-connect-titlebar-status.tor-connect-status-connected img { fill: var(--tor-text-color); stroke: var(--tor-text-color); } @@ -60,11 +72,11 @@ } @media (prefers-reduced-motion: no-preference) { - #tor-connect-titlebar-status.tor-connect-status-connected.tor-connect-status-animate-transition { + .tor-connect-titlebar-status.tor-connect-status-connected.tor-connect-status-animate-transition { transition: color 1000ms; } - #tor-connect-titlebar-status.tor-connect-status-connected.tor-connect-status-animate-transition img { + .tor-connect-titlebar-status.tor-connect-status-connected.tor-connect-status-animate-transition img { transition: fill 1000ms, stroke 1000ms; animation-name: onion-not-connected-to-connected; animation-delay: 200ms; ===================================== toolkit/components/torconnect/content/torConnectTitlebarStatus.inc.xhtml ===================================== @@ -0,0 +1,7 @@ +<html:div class="tor-connect-titlebar-status" role="status"> + <html:img + alt="" + src="chrome://global/content/torconnect/tor-not-connected-to-connected-animated.svg" + /> + <html:span class="tor-connect-titlebar-status-label"></html:span> +</html:div> ===================================== toolkit/components/torconnect/content/torConnectTitlebarStatus.js ===================================== @@ -3,21 +3,15 @@ */ var gTorConnectTitlebarStatus = { /** - * The status element in the title bar. + * The status elements and their labels. * - * @type {Element} + * @type {{status: Element, label: Element}[]} */ - node: null, - /** - * The status label. - * - * @type {Element} - */ - label: null, + _elements: [], /** * Whether we are connected, or null if the connection state is not yet known. * - * @type {bool?} + * @type {boolean?} */ connected: null, @@ -31,21 +25,21 @@ var gTorConnectTitlebarStatus = { this._strings = TorStrings.torConnect; - this.node = document.getElementById("tor-connect-titlebar-status"); - this.label = document.getElementById("tor-connect-titlebar-status-label"); + this._elements = Array.from( + document.querySelectorAll(".tor-connect-titlebar-status"), + element => { + return { + status: element, + label: element.querySelector(".tor-connect-titlebar-status-label"), + }; + } + ); // The title also acts as an accessible name for the role="status". - this.node.setAttribute("title", this._strings.titlebarStatusName); + for (const { status } of this._elements) { + status.setAttribute("title", this._strings.titlebarStatusName); + } - this._observeTopic = TorConnectTopics.StageChange; - this._stateListener = { - observe: (subject, topic) => { - if (topic !== this._observeTopic) { - return; - } - this._torConnectStateChanged(); - }, - }; - Services.obs.addObserver(this._stateListener, this._observeTopic); + Services.obs.addObserver(this, TorConnectTopics.StageChange); this._torConnectStateChanged(); }, @@ -54,7 +48,15 @@ var gTorConnectTitlebarStatus = { * De-initialize the component. */ uninit() { - Services.obs.removeObserver(this._stateListener, this._observeTopic); + Services.obs.removeObserver(this, TorConnectTopics.StageChange); + }, + + observe(subject, topic) { + switch (topic) { + case TorConnectTopics.StageChange: + this._torConnectStateChanged(); + break; + } }, /** @@ -67,7 +69,7 @@ var gTorConnectTitlebarStatus = { switch (TorConnect.stageName) { case TorConnectStage.Disabled: // Hide immediately. - this.node.hidden = true; + this._setHidden(true); return; case TorConnectStage.Bootstrapped: textId = "titlebarStatusConnected"; @@ -85,7 +87,9 @@ var gTorConnectTitlebarStatus = { } break; } - this.label.textContent = this._strings[textId]; + for (const { label } of this._elements) { + label.textContent = this._strings[textId]; + } if (this.connected !== connected) { // When we are transitioning from // this.connected = false @@ -104,11 +108,13 @@ var gTorConnectTitlebarStatus = { // // We only expect this latter case when opening a new window after // bootstrapping has already completed. See tor-browser#41850. - this.node.classList.toggle( - "tor-connect-status-animate-transition", - connected && this.connected !== null - ); - this.node.classList.toggle("tor-connect-status-connected", connected); + for (const { status } of this._elements) { + status.classList.toggle( + "tor-connect-status-animate-transition", + connected && this.connected !== null + ); + status.classList.toggle("tor-connect-status-connected", connected); + } this.connected = connected; if (connected) { this._startHiding(); @@ -119,10 +125,23 @@ var gTorConnectTitlebarStatus = { this._stopHiding(); } } - this.node.classList.toggle( - "tor-connect-status-potentially-blocked", - potentiallyBlocked - ); + for (const { status } of this._elements) { + status.classList.toggle( + "tor-connect-status-potentially-blocked", + potentiallyBlocked + ); + } + }, + + /** + * Hide or show the status. + * + * @param {boolean} hide - Whether to hide the status. + */ + _setHidden(hide) { + for (const { status } of this._elements) { + status.hidden = hide; + } }, /** @@ -134,7 +153,7 @@ var gTorConnectTitlebarStatus = { return; } this._hidingTimeout = setTimeout(() => { - this.node.hidden = true; + this._setHidden(true); }, 5000); }, @@ -146,6 +165,6 @@ var gTorConnectTitlebarStatus = { clearTimeout(this._hidingTimeout); this._hidingTimeout = 0; } - this.node.hidden = false; + this._setHidden(false); }, }; View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/23c8a66… -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/23c8a66… You're receiving this email because of your account on gitlab.torproject.org.
1 0
0 0
[Git][tpo/applications/mullvad-browser][mullvad-browser-140.4.0esr-15.0-1] Bug 1993166 - Improve origin attributes on opensearch. r=Standard8,urlbar-reviewers
by morgan (@morgan) 15 Oct '25

15 Oct '25
morgan pushed to branch mullvad-browser-140.4.0esr-15.0-1 at The Tor Project / Applications / Mullvad Browser Commits: 9795642d by Pier Angelo Vendrame at 2025-10-15T17:37:56+00:00 Bug 1993166 - Improve origin attributes on opensearch. r=Standard8,urlbar-reviewers Differential Revision: https://phabricator.services.mozilla.com/D268183 - - - - - 5 changed files: - browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs - toolkit/components/search/OpenSearchEngine.sys.mjs - toolkit/components/search/SearchEngine.sys.mjs - toolkit/components/search/SearchService.sys.mjs - toolkit/components/search/SearchUtils.sys.mjs Changes: ===================================== browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs ===================================== @@ -286,10 +286,22 @@ class ProviderContextualSearch extends ActionsProvider { let { type, engine } = this.#resultEngine; if (type == OPEN_SEARCH_ENGINE) { + let originAttributes; + try { + let currentURI = Services.io.newURI(queryContext.currentPage); + originAttributes = { + firstPartyDomain: Services.eTLD.getSchemelessSite(currentURI), + }; + } catch {} let openSearchEngineData = await lazy.loadAndParseOpenSearchEngine( - Services.io.newURI(engine.uri) + Services.io.newURI(engine.uri), + null, + originAttributes ); - engine = new lazy.OpenSearchEngine({ engineData: openSearchEngineData }); + engine = new lazy.OpenSearchEngine({ + engineData: openSearchEngineData, + originAttributes, + }); } this.#performSearch( ===================================== toolkit/components/search/OpenSearchEngine.sys.mjs ===================================== @@ -56,6 +56,8 @@ export class OpenSearchEngine extends SearchEngine { * @param {string} [options.faviconURL] * The website favicon, to be used if the engine data hasn't specified an * icon. + * @param {object} [options.originAttributes] + * The origin attributes to use to download additional resources. */ constructor(options = {}) { super({ @@ -68,7 +70,10 @@ export class OpenSearchEngine extends SearchEngine { }); if (options.faviconURL) { - this._setIcon(options.faviconURL, undefined, false).catch(e => + this._setIcon(options.faviconURL, { + override: false, + originAttributes: options.originAttributes, + }).catch(e => lazy.logConsole.error( `Error while setting icon for search engine ${options.engineData.name}:`, e.message @@ -77,7 +82,7 @@ export class OpenSearchEngine extends SearchEngine { } if (options.engineData) { - this.#setEngineData(options.engineData); + this.#setEngineData(options.engineData, options.originAttributes); // As this is a new engine, we must set the verification hash for the load // path set in the constructor. @@ -189,8 +194,10 @@ export class OpenSearchEngine extends SearchEngine { * * @param {OpenSearchProperties} data * The OpenSearch data. + * @param {object} originAttributes + * The origin attributes for any additional downloads */ - #setEngineData(data) { + #setEngineData(data, originAttributes) { let name = data.name.trim(); if (Services.search.getEngineByName(name)) { throw Components.Exception( @@ -258,11 +265,12 @@ export class OpenSearchEngine extends SearchEngine { } for (let image of data.images) { - this._setIcon(image.url, image.size).catch(e => - lazy.logConsole.error( - `Error while setting icon for search engine ${data.name}:`, - e.message - ) + this._setIcon(image.url, { size: image.size, originAttributes }).catch( + e => + lazy.logConsole.error( + `Error while setting icon for search engine ${data.name}:`, + e.message + ) ); } } ===================================== toolkit/components/search/SearchEngine.sys.mjs ===================================== @@ -585,15 +585,19 @@ export class SearchEngine { * @param {string} iconURL * A URI string pointing to the engine's icon. * Must have http[s], data, or moz-extension protocol. - * @param {number} [size] + * @param {object} options + * The options object + * @param {number} [options.size] * Width and height of the icon (determined automatically if not provided). - * @param {boolean} [override] + * @param {boolean} [options.override] * Whether the new URI should override an existing one. + * @param {object} [options.originAttributes] + * The origin attributes to use to load the icon. * @returns {Promise<void>} * Resolves when the icon was set. * Rejects with an Error if there was an error. */ - async _setIcon(iconURL, size, override = true) { + async _setIcon(iconURL, options = { override: true }) { lazy.logConsole.debug( "_setIcon: Setting icon url for", this.name, @@ -601,8 +605,12 @@ export class SearchEngine { limitURILength(iconURL) ); - [iconURL, size] = await this._downloadAndRescaleIcon(iconURL, size); - this._addIconToMap(iconURL, size, override); + let size; + [iconURL, size] = await this._downloadAndRescaleIcon(iconURL, { + size: options.size, + originAttributes: options.originAttributes, + }); + this._addIconToMap(iconURL, size, options.override); if (this._engineAddedToStore) { lazy.SearchUtils.notifyAction( @@ -620,18 +628,24 @@ export class SearchEngine { * @param {string} iconURL * A URI string pointing to the engine's icon. * Must have http[s], data, or moz-extension protocol. - * @param {number} [size] + * @param {object} options + * The options object + * @param {number} [options.size] * Width and height of the icon (determined automatically if not provided). + * @param {object} [options.originAttributes] + * The origin attributes to use to load the icon. * @returns {Promise<[string, number]>} * Resolves to [dataURL, size] if successful and rejects if there was an error. */ - async _downloadAndRescaleIcon(iconURL, size) { + async _downloadAndRescaleIcon(iconURL, options = {}) { let uri = lazy.SearchUtils.makeURI(iconURL); if (!uri) { throw new Error(`Invalid URI`); } + let size = options.size; + switch (uri.scheme) { case "moz-extension": { if (!size) { @@ -644,7 +658,10 @@ export class SearchEngine { case "data": case "http": case "https": { - let [byteArray, contentType] = await lazy.SearchUtils.fetchIcon(uri); + let [byteArray, contentType] = await lazy.SearchUtils.fetchIcon( + uri, + options.originAttributes + ); if (byteArray.length > lazy.SearchUtils.MAX_ICON_SIZE) { lazy.logConsole.debug( `Rescaling icon for search engine ${this.name}.` ===================================== toolkit/components/search/SearchService.sys.mjs ===================================== @@ -772,7 +772,11 @@ export class SearchService { null, originAttributes ); - engine = new lazy.OpenSearchEngine({ engineData, faviconURL: iconURL }); + engine = new lazy.OpenSearchEngine({ + engineData, + faviconURL: iconURL, + originAttributes, + }); } catch (ex) { throw Components.Exception( "addEngine: Error adding engine:\n" + ex, ===================================== toolkit/components/search/SearchUtils.sys.mjs ===================================== @@ -511,13 +511,19 @@ export var SearchUtils = { * * @param {string|nsIURI} uri * The URI to the icon. + * @param {object} [originAttributes] + * The origin attributes to download the icon. * @returns {Promise<[Uint8Array, string]>} * Resolves to an array containing the data and the mime type. * Rejects if the icon cannot be fetched. */ - async fetchIcon(uri) { + async fetchIcon(uri, originAttributes = null) { return new Promise((resolve, reject) => { - let chan = SearchUtils.makeChannel(uri, Ci.nsIContentPolicy.TYPE_IMAGE); + let chan = SearchUtils.makeChannel( + uri, + Ci.nsIContentPolicy.TYPE_IMAGE, + originAttributes + ); let listener = new SearchUtils.LoadListener( chan, /^image\//, View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/979… -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/979… You're receiving this email because of your account on gitlab.torproject.org.
1 0
0 0
[Git][tpo/applications/tor-browser][base-browser-140.4.0esr-15.0-1] Bug 1993166 - Improve origin attributes on opensearch. r=Standard8,urlbar-reviewers
by morgan (@morgan) 15 Oct '25

15 Oct '25
morgan pushed to branch base-browser-140.4.0esr-15.0-1 at The Tor Project / Applications / Tor Browser Commits: 803d875f by Pier Angelo Vendrame at 2025-10-15T17:37:02+00:00 Bug 1993166 - Improve origin attributes on opensearch. r=Standard8,urlbar-reviewers Differential Revision: https://phabricator.services.mozilla.com/D268183 - - - - - 5 changed files: - browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs - toolkit/components/search/OpenSearchEngine.sys.mjs - toolkit/components/search/SearchEngine.sys.mjs - toolkit/components/search/SearchService.sys.mjs - toolkit/components/search/SearchUtils.sys.mjs Changes: ===================================== browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs ===================================== @@ -286,10 +286,22 @@ class ProviderContextualSearch extends ActionsProvider { let { type, engine } = this.#resultEngine; if (type == OPEN_SEARCH_ENGINE) { + let originAttributes; + try { + let currentURI = Services.io.newURI(queryContext.currentPage); + originAttributes = { + firstPartyDomain: Services.eTLD.getSchemelessSite(currentURI), + }; + } catch {} let openSearchEngineData = await lazy.loadAndParseOpenSearchEngine( - Services.io.newURI(engine.uri) + Services.io.newURI(engine.uri), + null, + originAttributes ); - engine = new lazy.OpenSearchEngine({ engineData: openSearchEngineData }); + engine = new lazy.OpenSearchEngine({ + engineData: openSearchEngineData, + originAttributes, + }); } this.#performSearch( ===================================== toolkit/components/search/OpenSearchEngine.sys.mjs ===================================== @@ -56,6 +56,8 @@ export class OpenSearchEngine extends SearchEngine { * @param {string} [options.faviconURL] * The website favicon, to be used if the engine data hasn't specified an * icon. + * @param {object} [options.originAttributes] + * The origin attributes to use to download additional resources. */ constructor(options = {}) { super({ @@ -68,7 +70,10 @@ export class OpenSearchEngine extends SearchEngine { }); if (options.faviconURL) { - this._setIcon(options.faviconURL, undefined, false).catch(e => + this._setIcon(options.faviconURL, { + override: false, + originAttributes: options.originAttributes, + }).catch(e => lazy.logConsole.error( `Error while setting icon for search engine ${options.engineData.name}:`, e.message @@ -77,7 +82,7 @@ export class OpenSearchEngine extends SearchEngine { } if (options.engineData) { - this.#setEngineData(options.engineData); + this.#setEngineData(options.engineData, options.originAttributes); // As this is a new engine, we must set the verification hash for the load // path set in the constructor. @@ -189,8 +194,10 @@ export class OpenSearchEngine extends SearchEngine { * * @param {OpenSearchProperties} data * The OpenSearch data. + * @param {object} originAttributes + * The origin attributes for any additional downloads */ - #setEngineData(data) { + #setEngineData(data, originAttributes) { let name = data.name.trim(); if (Services.search.getEngineByName(name)) { throw Components.Exception( @@ -258,11 +265,12 @@ export class OpenSearchEngine extends SearchEngine { } for (let image of data.images) { - this._setIcon(image.url, image.size).catch(e => - lazy.logConsole.error( - `Error while setting icon for search engine ${data.name}:`, - e.message - ) + this._setIcon(image.url, { size: image.size, originAttributes }).catch( + e => + lazy.logConsole.error( + `Error while setting icon for search engine ${data.name}:`, + e.message + ) ); } } ===================================== toolkit/components/search/SearchEngine.sys.mjs ===================================== @@ -585,15 +585,19 @@ export class SearchEngine { * @param {string} iconURL * A URI string pointing to the engine's icon. * Must have http[s], data, or moz-extension protocol. - * @param {number} [size] + * @param {object} options + * The options object + * @param {number} [options.size] * Width and height of the icon (determined automatically if not provided). - * @param {boolean} [override] + * @param {boolean} [options.override] * Whether the new URI should override an existing one. + * @param {object} [options.originAttributes] + * The origin attributes to use to load the icon. * @returns {Promise<void>} * Resolves when the icon was set. * Rejects with an Error if there was an error. */ - async _setIcon(iconURL, size, override = true) { + async _setIcon(iconURL, options = { override: true }) { lazy.logConsole.debug( "_setIcon: Setting icon url for", this.name, @@ -601,8 +605,12 @@ export class SearchEngine { limitURILength(iconURL) ); - [iconURL, size] = await this._downloadAndRescaleIcon(iconURL, size); - this._addIconToMap(iconURL, size, override); + let size; + [iconURL, size] = await this._downloadAndRescaleIcon(iconURL, { + size: options.size, + originAttributes: options.originAttributes, + }); + this._addIconToMap(iconURL, size, options.override); if (this._engineAddedToStore) { lazy.SearchUtils.notifyAction( @@ -620,18 +628,24 @@ export class SearchEngine { * @param {string} iconURL * A URI string pointing to the engine's icon. * Must have http[s], data, or moz-extension protocol. - * @param {number} [size] + * @param {object} options + * The options object + * @param {number} [options.size] * Width and height of the icon (determined automatically if not provided). + * @param {object} [options.originAttributes] + * The origin attributes to use to load the icon. * @returns {Promise<[string, number]>} * Resolves to [dataURL, size] if successful and rejects if there was an error. */ - async _downloadAndRescaleIcon(iconURL, size) { + async _downloadAndRescaleIcon(iconURL, options = {}) { let uri = lazy.SearchUtils.makeURI(iconURL); if (!uri) { throw new Error(`Invalid URI`); } + let size = options.size; + switch (uri.scheme) { case "moz-extension": { if (!size) { @@ -644,7 +658,10 @@ export class SearchEngine { case "data": case "http": case "https": { - let [byteArray, contentType] = await lazy.SearchUtils.fetchIcon(uri); + let [byteArray, contentType] = await lazy.SearchUtils.fetchIcon( + uri, + options.originAttributes + ); if (byteArray.length > lazy.SearchUtils.MAX_ICON_SIZE) { lazy.logConsole.debug( `Rescaling icon for search engine ${this.name}.` ===================================== toolkit/components/search/SearchService.sys.mjs ===================================== @@ -772,7 +772,11 @@ export class SearchService { null, originAttributes ); - engine = new lazy.OpenSearchEngine({ engineData, faviconURL: iconURL }); + engine = new lazy.OpenSearchEngine({ + engineData, + faviconURL: iconURL, + originAttributes, + }); } catch (ex) { throw Components.Exception( "addEngine: Error adding engine:\n" + ex, ===================================== toolkit/components/search/SearchUtils.sys.mjs ===================================== @@ -511,13 +511,19 @@ export var SearchUtils = { * * @param {string|nsIURI} uri * The URI to the icon. + * @param {object} [originAttributes] + * The origin attributes to download the icon. * @returns {Promise<[Uint8Array, string]>} * Resolves to an array containing the data and the mime type. * Rejects if the icon cannot be fetched. */ - async fetchIcon(uri) { + async fetchIcon(uri, originAttributes = null) { return new Promise((resolve, reject) => { - let chan = SearchUtils.makeChannel(uri, Ci.nsIContentPolicy.TYPE_IMAGE); + let chan = SearchUtils.makeChannel( + uri, + Ci.nsIContentPolicy.TYPE_IMAGE, + originAttributes + ); let listener = new SearchUtils.LoadListener( chan, /^image\//, View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/803d875… -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/803d875… You're receiving this email because of your account on gitlab.torproject.org.
1 0
0 0
[Git][tpo/applications/tor-browser][tor-browser-140.4.0esr-15.0-1] Bug 1993166 - Improve origin attributes on opensearch. r=Standard8,urlbar-reviewers
by morgan (@morgan) 15 Oct '25

15 Oct '25
morgan pushed to branch tor-browser-140.4.0esr-15.0-1 at The Tor Project / Applications / Tor Browser Commits: 39767109 by Pier Angelo Vendrame at 2025-10-15T17:28:31+00:00 Bug 1993166 - Improve origin attributes on opensearch. r=Standard8,urlbar-reviewers Differential Revision: https://phabricator.services.mozilla.com/D268183 - - - - - 5 changed files: - browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs - toolkit/components/search/OpenSearchEngine.sys.mjs - toolkit/components/search/SearchEngine.sys.mjs - toolkit/components/search/SearchService.sys.mjs - toolkit/components/search/SearchUtils.sys.mjs Changes: ===================================== browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs ===================================== @@ -286,10 +286,22 @@ class ProviderContextualSearch extends ActionsProvider { let { type, engine } = this.#resultEngine; if (type == OPEN_SEARCH_ENGINE) { + let originAttributes; + try { + let currentURI = Services.io.newURI(queryContext.currentPage); + originAttributes = { + firstPartyDomain: Services.eTLD.getSchemelessSite(currentURI), + }; + } catch {} let openSearchEngineData = await lazy.loadAndParseOpenSearchEngine( - Services.io.newURI(engine.uri) + Services.io.newURI(engine.uri), + null, + originAttributes ); - engine = new lazy.OpenSearchEngine({ engineData: openSearchEngineData }); + engine = new lazy.OpenSearchEngine({ + engineData: openSearchEngineData, + originAttributes, + }); } this.#performSearch( ===================================== toolkit/components/search/OpenSearchEngine.sys.mjs ===================================== @@ -56,6 +56,8 @@ export class OpenSearchEngine extends SearchEngine { * @param {string} [options.faviconURL] * The website favicon, to be used if the engine data hasn't specified an * icon. + * @param {object} [options.originAttributes] + * The origin attributes to use to download additional resources. */ constructor(options = {}) { super({ @@ -68,7 +70,10 @@ export class OpenSearchEngine extends SearchEngine { }); if (options.faviconURL) { - this._setIcon(options.faviconURL, undefined, false).catch(e => + this._setIcon(options.faviconURL, { + override: false, + originAttributes: options.originAttributes, + }).catch(e => lazy.logConsole.error( `Error while setting icon for search engine ${options.engineData.name}:`, e.message @@ -77,7 +82,7 @@ export class OpenSearchEngine extends SearchEngine { } if (options.engineData) { - this.#setEngineData(options.engineData); + this.#setEngineData(options.engineData, options.originAttributes); // As this is a new engine, we must set the verification hash for the load // path set in the constructor. @@ -189,8 +194,10 @@ export class OpenSearchEngine extends SearchEngine { * * @param {OpenSearchProperties} data * The OpenSearch data. + * @param {object} originAttributes + * The origin attributes for any additional downloads */ - #setEngineData(data) { + #setEngineData(data, originAttributes) { let name = data.name.trim(); if (Services.search.getEngineByName(name)) { throw Components.Exception( @@ -258,11 +265,12 @@ export class OpenSearchEngine extends SearchEngine { } for (let image of data.images) { - this._setIcon(image.url, image.size).catch(e => - lazy.logConsole.error( - `Error while setting icon for search engine ${data.name}:`, - e.message - ) + this._setIcon(image.url, { size: image.size, originAttributes }).catch( + e => + lazy.logConsole.error( + `Error while setting icon for search engine ${data.name}:`, + e.message + ) ); } } ===================================== toolkit/components/search/SearchEngine.sys.mjs ===================================== @@ -585,15 +585,19 @@ export class SearchEngine { * @param {string} iconURL * A URI string pointing to the engine's icon. * Must have http[s], data, or moz-extension protocol. - * @param {number} [size] + * @param {object} options + * The options object + * @param {number} [options.size] * Width and height of the icon (determined automatically if not provided). - * @param {boolean} [override] + * @param {boolean} [options.override] * Whether the new URI should override an existing one. + * @param {object} [options.originAttributes] + * The origin attributes to use to load the icon. * @returns {Promise<void>} * Resolves when the icon was set. * Rejects with an Error if there was an error. */ - async _setIcon(iconURL, size, override = true) { + async _setIcon(iconURL, options = { override: true }) { lazy.logConsole.debug( "_setIcon: Setting icon url for", this.name, @@ -601,8 +605,12 @@ export class SearchEngine { limitURILength(iconURL) ); - [iconURL, size] = await this._downloadAndRescaleIcon(iconURL, size); - this._addIconToMap(iconURL, size, override); + let size; + [iconURL, size] = await this._downloadAndRescaleIcon(iconURL, { + size: options.size, + originAttributes: options.originAttributes, + }); + this._addIconToMap(iconURL, size, options.override); if (this._engineAddedToStore) { lazy.SearchUtils.notifyAction( @@ -620,18 +628,24 @@ export class SearchEngine { * @param {string} iconURL * A URI string pointing to the engine's icon. * Must have http[s], data, or moz-extension protocol. - * @param {number} [size] + * @param {object} options + * The options object + * @param {number} [options.size] * Width and height of the icon (determined automatically if not provided). + * @param {object} [options.originAttributes] + * The origin attributes to use to load the icon. * @returns {Promise<[string, number]>} * Resolves to [dataURL, size] if successful and rejects if there was an error. */ - async _downloadAndRescaleIcon(iconURL, size) { + async _downloadAndRescaleIcon(iconURL, options = {}) { let uri = lazy.SearchUtils.makeURI(iconURL); if (!uri) { throw new Error(`Invalid URI`); } + let size = options.size; + switch (uri.scheme) { case "moz-extension": { if (!size) { @@ -644,7 +658,10 @@ export class SearchEngine { case "data": case "http": case "https": { - let [byteArray, contentType] = await lazy.SearchUtils.fetchIcon(uri); + let [byteArray, contentType] = await lazy.SearchUtils.fetchIcon( + uri, + options.originAttributes + ); if (byteArray.length > lazy.SearchUtils.MAX_ICON_SIZE) { lazy.logConsole.debug( `Rescaling icon for search engine ${this.name}.` ===================================== toolkit/components/search/SearchService.sys.mjs ===================================== @@ -772,7 +772,11 @@ export class SearchService { null, originAttributes ); - engine = new lazy.OpenSearchEngine({ engineData, faviconURL: iconURL }); + engine = new lazy.OpenSearchEngine({ + engineData, + faviconURL: iconURL, + originAttributes, + }); } catch (ex) { throw Components.Exception( "addEngine: Error adding engine:\n" + ex, ===================================== toolkit/components/search/SearchUtils.sys.mjs ===================================== @@ -511,13 +511,19 @@ export var SearchUtils = { * * @param {string|nsIURI} uri * The URI to the icon. + * @param {object} [originAttributes] + * The origin attributes to download the icon. * @returns {Promise<[Uint8Array, string]>} * Resolves to an array containing the data and the mime type. * Rejects if the icon cannot be fetched. */ - async fetchIcon(uri) { + async fetchIcon(uri, originAttributes = null) { return new Promise((resolve, reject) => { - let chan = SearchUtils.makeChannel(uri, Ci.nsIContentPolicy.TYPE_IMAGE); + let chan = SearchUtils.makeChannel( + uri, + Ci.nsIContentPolicy.TYPE_IMAGE, + originAttributes + ); let listener = new SearchUtils.LoadListener( chan, /^image\//, View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/3976710… -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/3976710… You're receiving this email because of your account on gitlab.torproject.org.
1 0
0 0
[Git][tpo/applications/mullvad-browser][mullvad-browser-140.4.0esr-15.0-1] 12 commits: fixup! BB 41919: Letterboxing, add temporarily visible web content-size...
by henry (@henry) 15 Oct '25

15 Oct '25
henry pushed to branch mullvad-browser-140.4.0esr-15.0-1 at The Tor Project / Applications / Mullvad Browser Commits: 81be023b by Henry Wilkes at 2025-10-15T18:07:04+01:00 fixup! BB 41919: Letterboxing, add temporarily visible web content-size indicator on window resizing. TB 44214: Fix letterboxing status indicator for RTL. - - - - - deed64ab by Henry Wilkes at 2025-10-15T18:07:05+01:00 fixup! BB 32308: Use direct browser sizing for letterboxing. TB 44214: Drop unnecessary CSS rules. - - - - - f18fc8f5 by Henry Wilkes at 2025-10-15T18:07:06+01:00 fixup! BB 32308: Use direct browser sizing for letterboxing. TB 44214: Use CSS logical positions for the status panel, rather than "left" and "right". - - - - - 77ea2cff by Henry Wilkes at 2025-10-15T18:07:06+01:00 fixup! BB 32308: Use direct browser sizing for letterboxing. TB 44214: Drop upstream's rules for placing content. - - - - - 725f9b44 by Henry Wilkes at 2025-10-15T18:07:07+01:00 fixup! BB 32308: Use direct browser sizing for letterboxing. TB 44214: Drop letterboxing gradient. - - - - - c5acc4f7 by Henry Wilkes at 2025-10-15T18:07:08+01:00 fixup! BB 32308: Use direct browser sizing for letterboxing. TB 44214: Separate out the essential --letterboxing-width and --letterboxing-height rules into their own .letterboxing block, to have the property values set on. - - - - - d3f14e86 by Henry Wilkes at 2025-10-15T18:07:09+01:00 fixup! BB 32308: Use direct browser sizing for letterboxing. TB 44214: Use CSS nesting. Part 1. - - - - - f622ced5 by Henry Wilkes at 2025-10-15T18:07:09+01:00 fixup! BB 32308: Use direct browser sizing for letterboxing. TB 44214: Use CSS nesting. Part 2. - - - - - 9bd8b0b8 by Henry Wilkes at 2025-10-15T18:07:10+01:00 fixup! BB 32308: Use direct browser sizing for letterboxing. TB 44214: Move the letterboxing classes one element up from tabpanels to tabbox. This is because we need to restyle the tabbox. - - - - - f6b94a03 by Henry Wilkes at 2025-10-15T18:07:11+01:00 fixup! BB 32308: Use direct browser sizing for letterboxing. TB 44214: Use a CSS class to show/hide the letterboxing border, rather than setting the border-radius in javascript. - - - - - 9f0d03ed by Henry Wilkes at 2025-10-15T18:07:12+01:00 fixup! BB 32308: Use direct browser sizing for letterboxing. TB 44214: Rename CSS variable from top-radius to radius-top. This is closer to what upstream has done recently for tokens, where higher specificity is appended. - - - - - a11336fe by Henry Wilkes at 2025-10-15T18:07:12+01:00 fixup! BB 32308: Use direct browser sizing for letterboxing. TB 44214: Update letterboxing styling for ESR 140. - - - - - 4 changed files: - browser/base/content/browser-fullScreenAndPointerLock.js - browser/themes/shared/tabbrowser/content-area.css - toolkit/components/resistfingerprinting/RFPHelper.sys.mjs - toolkit/components/resistfingerprinting/content/letterboxing.css Changes: ===================================== browser/base/content/browser-fullScreenAndPointerLock.js ===================================== @@ -879,7 +879,13 @@ var FullScreen = { } this._isChromeCollapsed = false; - Services.obs.notifyObservers(null, "fullscreen-nav-toolbox", "shown"); + // Need a subject to know which window this applies to. + // Base browser patch can be dropped after bugzilla bug 1992036. + Services.obs.notifyObservers( + gNavToolbox, + "fullscreen-nav-toolbox", + "shown" + ); }, hideNavToolbox(aAnimate = false) { @@ -943,7 +949,13 @@ var FullScreen = { gNavToolbox.style.marginTop = -gNavToolbox.getBoundingClientRect().height + "px"; this._isChromeCollapsed = true; - Services.obs.notifyObservers(null, "fullscreen-nav-toolbox", "hidden"); + // Need a subject to know which window this applies to. + // Base browser patch can be dropped after bugzilla bug 1880918. + Services.obs.notifyObservers( + gNavToolbox, + "fullscreen-nav-toolbox", + "hidden" + ); MousePosTracker.removeListener(this); }, ===================================== browser/themes/shared/tabbrowser/content-area.css ===================================== @@ -242,13 +242,17 @@ } } -#statuspanel[type=letterboxingStatus] > #statuspanel-label, -#statuspanel[previoustype=letterboxingStatus][inactive] > #statuspanel-label { +#statuspanel:is([type=letterboxingStatus], [previoustype=letterboxingStatus][inactive]) > #statuspanel-label { background-image: url("chrome://browser/skin/window.svg"); background-size: 1em; background-repeat: no-repeat; - background-position-x: .5em; background-position-y: center; + background-position-x: left .5em; + + &:-moz-locale-dir(rtl) { + background-position-x: right .5em; + } + padding-inline-start: 2em; -moz-context-properties: fill; fill: var(--color-accent-primary); ===================================== toolkit/components/resistfingerprinting/RFPHelper.sys.mjs ===================================== @@ -18,8 +18,6 @@ const kPrefLetterboxingTesting = "privacy.resistFingerprinting.letterboxing.testing"; const kPrefLetterboxingVcenter = "privacy.resistFingerprinting.letterboxing.vcenter"; -const kPrefLetterboxingGradient = - "privacy.resistFingerprinting.letterboxing.gradient"; const kPrefLetterboxingDidForceSize = "privacy.resistFingerprinting.letterboxing.didForceSize"; const kPrefLetterboxingRememberSize = @@ -28,10 +26,17 @@ const kPrefLetterboxingRememberSize = const kTopicDOMWindowOpened = "domwindowopened"; const kTopicDOMWindowClosed = "domwindowclosed"; +const kTopicFullscreenNavToolbox = "fullscreen-nav-toolbox"; + const kPrefResizeWarnings = "privacy.resistFingerprinting.resizeWarnings"; +const kPrefVerticalTabs = "sidebar.verticalTabs"; + const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + Color: "resource://gre/modules/Color.sys.mjs", +}); ChromeUtils.defineLazyGetter(lazy, "logConsole", () => console.createInstance({ prefix: "RFPHelper", @@ -155,7 +160,8 @@ class _RFPHelper { Services.prefs.addObserver(kPrefResistFingerprinting, this); Services.prefs.addObserver(kPrefLetterboxing, this); Services.prefs.addObserver(kPrefLetterboxingVcenter, this); - Services.prefs.addObserver(kPrefLetterboxingGradient, this); + Services.prefs.addObserver(kPrefVerticalTabs, this); + Services.obs.addObserver(this, kTopicFullscreenNavToolbox); XPCOMUtils.defineLazyPreferenceGetter( this, @@ -188,9 +194,10 @@ class _RFPHelper { // Remove unconditional observers Services.prefs.removeObserver(kPrefResistFingerprinting, this); - Services.prefs.removeObserver(kPrefLetterboxingGradient, this); Services.prefs.removeObserver(kPrefLetterboxingVcenter, this); Services.prefs.removeObserver(kPrefLetterboxing, this); + Services.prefs.removeObserver(kPrefVerticalTabs, this); + Services.obs.removeObserver(this, kTopicFullscreenNavToolbox); // Remove the RFP observers, swallowing exceptions if they weren't present this._removeLanguagePrefObservers(); } @@ -212,6 +219,15 @@ class _RFPHelper { case kTopicDOMWindowClosed: this._handleDOMWindowClosed(subject); break; + case kTopicFullscreenNavToolbox: + // The `subject` is the gNavToolbox. + // Record whether the toobox has been hidden when the browser (not + // content) is in fullscreen. + subject.ownerGlobal.gBrowser.tabbox.classList.toggle( + "letterboxing-nav-toolbox-hidden", + data === "hidden" + ); + break; default: break; } @@ -226,6 +242,13 @@ class _RFPHelper { resizeObserver.observe(browser.parentElement); break; } + case "nativethemechange": + // NOTE: "nativethemechange" seems to always be sent after + // "windowlwthemeupdate". So all the lwtheme CSS properties should be + // set to the new theme's values already, so we don't need to wait for + // windowlwthemeupdate. + this._updateLetterboxingColors(aMessage.currentTarget, true); + break; default: break; } @@ -245,9 +268,13 @@ class _RFPHelper { Services.prefs.clearUserPref(kPrefLetterboxingDidForceSize); // fall-through case kPrefLetterboxingVcenter: - case kPrefLetterboxingGradient: this._handleLetterboxingPrefChanged(); break; + case kPrefVerticalTabs: + if (this.letterboxingEnabled) { + forEachWindow(win => this._updateLetterboxingColors(win)); + } + break; default: break; } @@ -452,7 +479,7 @@ class _RFPHelper { // If not already cached on the document object, traverse the CSSOM and // find the rule applying the default letterboxing styles to browsers // preemptively in order to beat race conditions on tab/window creation - return (document._letterboxingMarginsRule ||= (() => { + return (document._letterboxingDefaultRule ||= (() => { const LETTERBOX_CSS_SELECTOR = ".letterboxing"; const LETTERBOX_CSS_URL = "chrome://global/content/resistfingerprinting/letterboxing.css"; @@ -688,26 +715,22 @@ class _RFPHelper { if (lastRoundedSize) { // Check whether the letterboxing margin is less than the border radius, - // and if so flatten the borders. - let borderRadius = parseInt( - win - .getComputedStyle(browserContainer) - .getPropertyValue("--letterboxing-border-radius") + // and if so do not show an outline. + const gapVertical = parentHeight - lastRoundedSize.height; + const gapHorizontal = parentWidth - lastRoundedSize.width; + browserParent.classList.toggle( + "letterboxing-show-outline", + gapVertical >= this._letterboxingBorderRadius || + gapHorizontal >= this._letterboxingBorderRadius + ); + // When the Letterboxing area is top-aligned, only show the sidebar corner + // if there is enough horizontal space. + // The factor of 4 is from the horizontal centre-alignment and wanting + // enough space for twice the corner radius. + browserParent.classList.toggle( + "letterboxing-show-sidebar-corner", + gapHorizontal >= 4 * this._letterboxingBorderRadius ); - if ( - borderRadius && - parentWidth - lastRoundedSize.width < borderRadius && - parentHeight - lastRoundedSize.height < borderRadius - ) { - borderRadius = 0; - } else { - borderRadius = ""; - } - styleChanges.queueIfNeeded(browserParent, { - "--letterboxing-decorator-visibility": - borderRadius === 0 ? "hidden" : "", - "--letterboxing-border-radius": borderRadius, - }); if (win.gBrowser.selectedBrowser == aBrowser) { const updateStatus = async args => { win.XULBrowserWindow.letterboxingStatus = args @@ -769,20 +792,31 @@ class _RFPHelper { _resetContentSize(aBrowser) { aBrowser.parentElement.classList.add("exclude-letterboxing"); + aBrowser.parentElement.classList.remove( + "letterboxing-show-outline", + "letterboxing-show-sidebar-corner" + ); } _updateSizeForTabsInWindow(aWindow) { let tabBrowser = aWindow.gBrowser; - tabBrowser.tabpanels?.classList.add("letterboxing"); - tabBrowser.tabpanels?.classList.toggle( + tabBrowser.tabbox.classList.add("letterboxing"); + tabBrowser.tabbox.classList.toggle( "letterboxing-vcenter", Services.prefs.getBoolPref(kPrefLetterboxingVcenter, false) ); - tabBrowser.tabpanels?.classList.toggle( - "letterboxing-gradient", - Services.prefs.getBoolPref(kPrefLetterboxingGradient, false) - ); + if (this._letterboxingBorderRadius === undefined && tabBrowser.tabbox) { + // Cache the value since it is not expected to change in a session for any + // window. + this._letterboxingBorderRadius = Math.ceil( + parseFloat( + aWindow + .getComputedStyle(tabBrowser.tabbox) + .getPropertyValue("--letterboxing-border-radius") + ) + ); + } for (let tab of tabBrowser.tabs) { let browser = tab.linkedBrowser; @@ -791,7 +825,7 @@ class _RFPHelper { // We need to add this class late because otherwise new windows get // maximized. aWindow.setTimeout(() => { - tabBrowser.tabpanels?.classList.add("letterboxing-ready"); + tabBrowser.tabbox.classList.add("letterboxing-ready"); if (!aWindow._rfpOriginalSize) { this._recordWindowSize(aWindow); } @@ -869,6 +903,247 @@ class _RFPHelper { this._resizeObservers.set(aWindow, resizeObserver); // Rounding the content viewport. this._updateSizeForTabsInWindow(aWindow); + + this._updateLetterboxingColors(aWindow, true); + aWindow.addEventListener("nativethemechange", this); + } + + /** + * Convert a CSS property to its RGBA value. + * + * @param {Window} win - The window for the element. + * @param {CSSStyleDeclaration} style - The computed style for the element we + * want to grab the color from. + * @param {string} property - The name of the property we want. + * + * @returns {InspectorRGBATuple} - The RGBA color. The "r", "g", "b" fields + * are relative to the 0-255 color range. The "a" field is in the 0-1 range. + */ + _convertToRGBA(win, style, property) { + let cssColor = style.getPropertyValue(property); + if (!cssColor) { + lazy.logConsole.error(`Missing color "${property}"`); + return { r: 0, g: 0, b: 0, a: 0 }; + } + const currentColorRegex = + /(^|[^a-zA-Z0-9_-])currentColor($|[^a-zA-Z0-9_-])/g; + if (currentColorRegex.test(cssColor)) { + const currentColor = style.color; + cssColor = cssColor.replace(currentColorRegex, (_, pre, post) => { + return pre + currentColor + post; + }); + lazy.logConsole.debug( + "Replaced currentColor.", + property, + currentColor, + cssColor + ); + } + /* Can drop the document argument after bugzilla bug 1973684 (142). */ + const colorRGBA = win.InspectorUtils.colorToRGBA(cssColor, win.document); + if (!colorRGBA) { + lazy.logConsole.error( + `Failed to convert "${property}" color (${cssColor}) to RGBA` + ); + return { r: 0, g: 0, b: 0, a: 0 }; + } + return colorRGBA; + } + + /** + * Compose two colors with alpha values on top of each other. + * + * @param {InspectorRGBATuple} topRGBA - The color to place on the top. + * @param {InspectorRGBATuple} bottomRGBA - The color to place on the bottom. + * + * @returns {InspectorRGBATuple} - The composed color. + */ + _composeRGBA(topRGBA, bottomRGBA) { + const topA = Math.max(0, Math.min(1, topRGBA.a)); + const bottomA = Math.max(0, Math.min(1, bottomRGBA.a)); + const a = topA + bottomA - topA * bottomA; // Should be 1 if either is 1. + if (a === 0) { + return { r: 0, g: 0, b: 0, a }; + } + const ret = { a }; + for (const field of ["r", "g", "b"]) { + ret[field] = + (topRGBA[field] * topA + bottomRGBA[field] * bottomA * (1 - topA)) / a; + } + return ret; + } + + /** + * Calculate the urlbar's container opaque background color, removing any + * transparency. + * + * @param {Window} win - The window to calculate the color for. + * @param {CSSStyleDeclaration} style - The computed style for the #nav-bar + * element. + * + * @returns {InspectorRGBATuple} - The calculated color, which will be opaque. + */ + _calculateUrlbarContainerColor(win, style) { + let colorRGBA; + if (!Services.prefs.getBoolPref(kPrefVerticalTabs)) { + lazy.logConsole.debug("Toolbar background used."); + colorRGBA = this._convertToRGBA(win, style, "--toolbar-bgcolor"); + if (colorRGBA.a === 1) { + return colorRGBA; + } + } else { + // The urlbar only has the toolbox colour. + colorRGBA = { r: 0, g: 0, b: 0, a: 0 }; + } + let toolboxHasBackgroundImage = false; + const isLwTheme = win.document.documentElement.hasAttribute("lwtheme"); + if (isLwTheme) { + for (const prop of ["--lwt-header-image", "--lwt-additional-images"]) { + const headerImage = style.getPropertyValue(prop); + if (headerImage && headerImage !== "none") { + // The theme sets a background image behind the urlbar. No easy way to + // derive a single colour from this. + toolboxHasBackgroundImage = true; + lazy.logConsole.debug( + "Toolbox has background image.", + prop, + headerImage + ); + break; + } + } + } + if (!toolboxHasBackgroundImage) { + lazy.logConsole.debug("Toolbox background used."); + colorRGBA = this._composeRGBA( + colorRGBA, + this._convertToRGBA(win, style, "--toolbox-bgcolor") + ); + if (colorRGBA.a === 1) { + return colorRGBA; + } + } + + // Determine whether the urlbar is dark. + // At this point, the urlbar background has some transparency, likely on top + // of an image. + // We use the theme's text colour to figure out whether the urlbar + // background is overall meant to be light or dark. Unlike the urlbar, we + // expect this colour to be (almost) opaque. + const textRGBA = this._convertToRGBA(win, style, "--toolbar-field-color"); + const textColor = new lazy.Color(textRGBA.r, textRGBA.g, textRGBA.b); + if (textColor.relativeLuminance >= 0.5) { + // Light text, so assume it has a dark background. + // Combine with a generic opaque dark colour. Copied from "frame" for the + // built-in dark theme. + lazy.logConsole.debug("Generic dark background used."); + const darkFrameRGBA = { r: 28, g: 27, b: 34, a: 1 }; + return this._composeRGBA(colorRGBA, darkFrameRGBA); + } + // Combine with an opaque light colour. Copied from "frame" for the built-in + // light theme. + lazy.logConsole.debug("Generic light background used."); + const lightFrameRGBA = { r: 234, g: 234, b: 237, a: 1 }; + return this._composeRGBA(colorRGBA, lightFrameRGBA); + } + + /** + * Update the Letterboxing colors and related classes, or clear them if + * Letterboxing is not enabled. + * + * @param {Window} win - The window to update the colors for. + * @param {boolean} letterboxingEnabled - Whether Letterboxing is enabled. + */ + _updateLetterboxingColors(win, letterboxingEnabled) { + let urlbarBackgroundRGBA; + let urlbarTextRGBA; + let contentSeparatorRGBA; + let urlbarBackgroundDark = false; + let lowBackgroundOutlineContrast = false; + + if (letterboxingEnabled) { + // Want the effective colour of various elements without any alpha values + // so they can be used consistently. + const navbarStyle = win.getComputedStyle( + win.document.getElementById("nav-bar") + ); + const containerRGBA = this._calculateUrlbarContainerColor( + win, + navbarStyle + ); + urlbarBackgroundRGBA = this._composeRGBA( + this._convertToRGBA( + win, + navbarStyle, + "--toolbar-field-background-color" + ), + containerRGBA + ); + urlbarTextRGBA = this._composeRGBA( + this._convertToRGBA(win, navbarStyle, "--toolbar-field-color"), + urlbarBackgroundRGBA + ); + /* Separator between the urlbar container #nav-bar and the tabbox. */ + const tabboxStyle = win.getComputedStyle(win.gBrowser.tabbox); + contentSeparatorRGBA = this._composeRGBA( + this._convertToRGBA( + win, + tabboxStyle, + "--chrome-content-separator-color" + ), + containerRGBA + ); + const bgColor = new lazy.Color( + urlbarBackgroundRGBA.r, + urlbarBackgroundRGBA.g, + urlbarBackgroundRGBA.b + ); + const outlineColor = new lazy.Color( + contentSeparatorRGBA.r, + contentSeparatorRGBA.g, + contentSeparatorRGBA.b + ); + const contrastRatio = bgColor.contrastRatio(outlineColor); + lazy.logConsole.debug( + "Outline-background contrast ratio.", + contrastRatio + ); + urlbarBackgroundDark = bgColor.relativeLuminance < 0.5; + /* Very low contrast ratio. For reference the default light theme has + * a contrast ratio of ~1.1. */ + lowBackgroundOutlineContrast = contrastRatio < 1.05; + } + for (const { name, colorRGBA } of [ + { + name: "--letterboxing-urlbar-text-color", + colorRGBA: urlbarTextRGBA, + }, + { + name: "--letterboxing-urlbar-background-color", + colorRGBA: urlbarBackgroundRGBA, + }, + { + name: "--letterboxing-content-separator-color", + colorRGBA: contentSeparatorRGBA, + }, + ]) { + if (letterboxingEnabled) { + win.gBrowser.tabbox.style.setProperty( + name, + `rgb(${colorRGBA.r}, ${colorRGBA.g}, ${colorRGBA.b})` + ); + } else { + win.gBrowser.tabbox.style.removeProperty(name); + } + } + win.gBrowser.tabbox.classList.toggle( + "letterboxing-urlbar-background-dark", + urlbarBackgroundDark + ); + win.gBrowser.tabbox.classList.toggle( + "letterboxing-low-background-outline-contrast", + lowBackgroundOutlineContrast + ); } _detachWindow(aWindow) { @@ -886,7 +1161,7 @@ class _RFPHelper { aWindow.removeEventListener("TabOpen", this); // revert tabpanel's style to default - tabBrowser.tabpanels?.classList.remove("letterboxing"); + tabBrowser.tabbox.classList.remove("letterboxing"); // and restore default size on each browser element for (let tab of tabBrowser.tabs) { @@ -896,6 +1171,9 @@ class _RFPHelper { aWindow.removeEventListener("dblclick", this._onWindowDoubleClick); delete aWindow.shrinkToLetterbox; aWindow.removeEventListener("sizemodechange", windowResizeHandler); + + aWindow.removeEventListener("nativethemechange", this); + this._updateLetterboxingColors(aWindow, false); } _handleDOMWindowOpened(win) { ===================================== toolkit/components/resistfingerprinting/content/letterboxing.css ===================================== @@ -7,17 +7,106 @@ * RFPHelper.sys.mjs (LETTERBOX_CSS_SELECTOR and LETTERBOX_CSS_URL, * respectively), where --letterboxing-width & --letterboxing-height are * actually set. + * Keep this block first and separate to the rules that do not necessarily + * require --letterboxing-width or --letterboxing-height. */ .letterboxing { - --letterboxing-bgcolor: var(--tabpanel-background-color); - --letterboxing-border-radius: 8px; - --letterboxing-border-top-radius: 0; + .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing) > browser { + width: var(--letterboxing-width) !important; + height: var(--letterboxing-height) !important; + } +} + +#tabbrowser-tabbox.letterboxing { + --letterboxing-bgcolor: var(--background-color-canvas); + /* Match the border radius used for the sidebar. */ + --letterboxing-border-radius: var(--border-radius-medium); + --letterboxing-border-radius-top: 0; --letterboxing-vertical-alignment: start; - --letterboxing-shadow-color: rgba(12, 12, 13, 0.10); - --letterboxing-gradient-color1: var(--letterboxing-bgcolor); - --letterboxing-gradient-color2: color-mix(in srgb, var(--chrome-content-separator-color) 50%, var(--letterboxing-bgcolor)); - --letterboxing-border-color: var(--letterboxing-bgcolor); - --letterboxing-decorator-visibility: visible; + --letterboxing-shadow: none; + --letterboxing-outline-color: var(--border-color); + --letterboxing-outline-width: 1px; + + @media not ((prefers-contrast) or (forced-colors)) { + /* Match the #sidebar outline width. */ + --letterboxing-outline-width: 0.5px; + --letterboxing-shadow-color: rgba(58, 57, 68, 0.20); + --letterboxing-shadow: 0 2px 14px 0 var(--letterboxing-shadow-color); + + /* Match the effective urlbar background colour. */ + --letterboxing-bgcolor: var(--letterboxing-urlbar-background-color); + /* Match the effective colour of the separator between the urlbar container + * and the content. */ + --letterboxing-outline-color: var(--letterboxing-content-separator-color); + + &.letterboxing-urlbar-background-dark { + --letterboxing-shadow-color: #15141a; + } + + &.letterboxing-low-background-outline-contrast { + /* The default content separator colour has insufficient contrast. */ + --letterboxing-outline-color: color-mix(in srgb, var(--letterboxing-content-separator-color) 90%, black); + + &.letterboxing-urlbar-background-dark { + /* Lighten the colour. */ + --letterboxing-outline-color: color-mix(in srgb, var(--letterboxing-content-separator-color) 90%, white); + } + } + } + + @media (prefers-contrast) and (not (forced-colors)) { + :root[lwtheme] & { + /* User with prefers-contrast coming from the system settings, but also an + * installed theme. */ + --letterboxing-bgcolor: var(--letterboxing-urlbar-background-color); + /* Presumably a user with prefers-contrast and a custom theme has chosen + * a theme where the contrast between the urlbar and the text is + * sufficiently high or low. */ + --letterboxing-outline-color: var(--letterboxing-urlbar-text-color); + } + } + + background: var(--letterboxing-bgcolor); + + &:has(.deck-selected .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing).letterboxing-show-outline) { + /* Letterboxing outline is visible for the current tab. Replace the usual + * outline to match the Letterboxing outline. For most scenarios, this + * should be mostly the same colour as when Letterboxing is not visible. But + * it may make a difference for some theme combinations. */ + outline-color: var(--letterboxing-outline-color); + outline-width: var(--letterboxing-outline-width); + } + + #tabbrowser-tabpanels { + /* Override the --tabpanel-background-color. + * Also, make sure this remains transparent, otherwise it will overlap the + * parent's corner's border-radius due to it's "position: relative" rule. */ + /* TODO: FIX this for newtab pages. tor-browser#44085 */ + background: transparent; + } + + /* stylelint-disable-next-line media-query-no-invalid */ + @media -moz-pref("sidebar.revamp") { + :root:not([inDOMFullscreen]) &[sidebar-shown]:not(.letterboxing-nav-toolbox-hidden):is( + /* When the Letterboxing area is aligned to the top, show the rounded + * corner if there is enough vertical space between the sidebar and the + * browser element, which is not rounded at the top. */ + :not(.letterboxing-vcenter):has(.deck-selected .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing).letterboxing-show-sidebar-corner), + /* When the Letterboxing area is aligned to the centre, show the rounded + * corner if the Letterboxing border is shown. */ + .letterboxing-vcenter:has(.deck-selected .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing).letterboxing-show-outline) + ) { + /* stylelint-disable-next-line media-query-no-invalid */ + @media -moz-pref("sidebar.position_start") { + border-start-start-radius: var(--letterboxing-border-radius); + } + + /* stylelint-disable-next-line media-query-no-invalid */ + @media not -moz-pref("sidebar.position_start") { + border-start-end-radius: var(--letterboxing-border-radius); + } + } + } .browserContainer { /* @@ -26,101 +115,71 @@ * doesn't get notified on horizontal shrinking. */ overflow: hidden; - background: var(--letterboxing-bgcolor); } - .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing) > browser { - box-shadow: 0 4px 8px 0 var(--letterboxing-shadow-color); - border-radius: var(--letterboxing-border-radius); - border-top-left-radius: var(--letterboxing-border-top-radius); - border-top-right-radius: var(--letterboxing-border-top-radius); - width: var(--letterboxing-width) !important; - height: var(--letterboxing-height) !important; - background: var(--letterboxing-gradient-color2); + &.letterboxing-vcenter { + --letterboxing-border-radius-top: var(--letterboxing-border-radius); + --letterboxing-vertical-alignment: center; } } -:root:not([inDOMFullscreen]) .letterboxing.letterboxing-ready .browserContainer:not(.responsive-mode) - > .browserStack:not(.exclude-letterboxing) { - place-content: start center; -} - -.browserDecorator { - display: none; - pointer-events: none; - background: transparent; - position: relative; - z-index: 1; -} +.browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing) { + :root:not([inDOMFullscreen]) .letterboxing.letterboxing-ready & { + place-content: var(--letterboxing-vertical-alignment) center; + } -.letterboxing.letterboxing-vcenter .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing) { - --letterboxing-border-top-radius: var(--letterboxing-border-radius); - --letterboxing-vertical-alignment: center; -} + :root:not([inDOMFullscreen]) .letterboxing &.letterboxing-show-outline { + browser { + /* We use clip-path rather than border-radius because border-radius on its + * own leads to rendering artefacts in the corners (tested with GNOME). + * See tor-browser#44214 (comment 3262962). */ + /* TODO: Use border-radius once bugzilla bug 1991874 is resolved. */ + clip-path: rect(auto auto auto auto round var(--letterboxing-border-radius-top) var(--letterboxing-border-radius-top) var(--letterboxing-border-radius) var(--letterboxing-border-radius)); + } -.letterboxing.letterboxing-gradient .browserContainer { - background: linear-gradient(283deg, var(--letterboxing-gradient-color1) 0%, var(--letterboxing-gradient-color2) 100%), var(--letterboxing-bgcolor); -} + .browserDecorator { + /* Need a separate browserDecorator element because the clip-path on the + * browser would exclude the outline and box-shadow. */ + /* TODO: Move these rules to the browser element once bugzilla bug 1991874 + * is resolved, and drop browserDecorator. */ + display: block; + border-radius: var(--letterboxing-border-radius-top) var(--letterboxing-border-radius-top) var(--letterboxing-border-radius) var(--letterboxing-border-radius); + /* NOTE: The top outline will not be visible when this is aligned to the + * top. */ + outline: var(--letterboxing-outline-width) solid var(--letterboxing-outline-color); + box-shadow: var(--letterboxing-shadow); + } -:root:not([inDOMFullscreen]) .letterboxing .browserContainer:not(.responsive-mode) - > .browserStack:not(.exclude-letterboxing) - > .browserDecorator { - display: initial; - visibility: var(--letterboxing-decorator-visibility); - border-radius: var(--letterboxing-border-radius); - border-top-left-radius: var(--letterboxing-border-top-radius); - border-top-right-radius: var(--letterboxing-border-top-radius); - box-shadow: var(--letterboxing-border-color) 0 0 .1px inset, var(--letterboxing-border-color) 0 0 .1px; - border: .1px solid var(--letterboxing-border-color); - outline: .1px solid var(--letterboxing-bgcolor); - height: calc(var(--letterboxing-height) + 1px); - top: -1px; -} + #statuspanel:not([mirror]) #statuspanel-label { + border-end-start-radius: var(--letterboxing-border-radius); + } -.letterboxing-vcenter .browserDecorator { - height: auto !important; - top: 0 !important; -} + #statuspanel[mirror] #statuspanel-label { + border-end-end-radius: var(--letterboxing-border-radius); + } + } -/* - Align status bar with content. - TODO: switch to nested CSS selectors for conciseness when available (Firefox >= 117) -*/ -.letterboxing .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing) - > #statuspanel:not([hidden]) { - position: relative; - place-self: end left; - left: 0; - right: 0; - z-index: 2; - --letterboxing-status-left-radius: var(--letterboxing-border-radius); - --letterboxing-status-right-radius: 0; -} -.letterboxing .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing) - > #statuspanel:not([mirror]):-moz-locale-dir(rtl), -.letterboxing .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing) - > #statuspanel[mirror]:-moz-locale-dir(ltr) { - left: 0; - right: 0; - --letterboxing-status-right-radius: var(--letterboxing-border-radius); - --letterboxing-status-left-radius: 0; - justify-self: right; -} + #statuspanel { + position: relative; + place-self: end start; + z-index: 2; -.letterboxing .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing) -#statuspanel-label { - border-radius: 0 0 var(--letterboxing-status-right-radius) var(--letterboxing-status-left-radius); - margin: 0; - border: 1px solid var(--letterboxing-border-color); - max-width: calc(var(--letterboxing-width) * .5); -} + &[mirror] { + justify-self: end; + } + } -browser:fullscreen { - --letterboxing-border-top-radius: 0; - --letterboxing-border-radius: 0; + #statuspanel-label { + margin: 0; + outline: var(--letterboxing-outline-width) solid var(--letterboxing-outline-color); + max-width: calc(var(--letterboxing-width) * .5); + } } -:root:not([inDOMFullscreen]) .letterboxing.letterboxing-ready .browserContainer:not(.responsive-mode) - > .browserStack:not(.exclude-letterboxing) { - place-content: var(--letterboxing-vertical-alignment) center; +.browserDecorator { + display: none; + pointer-events: none; + background: transparent; + position: relative; + z-index: 1; } View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/42… -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/42… You're receiving this email because of your account on gitlab.torproject.org.
1 0
0 0
  • ← Newer
  • 1
  • 2
  • 3
  • 4
  • 5
  • ...
  • 14
  • Older →

HyperKitty Powered by HyperKitty version 1.3.12.