[tbb-commits] [Git][tpo/applications/tor-browser][tor-browser-115.10.0esr-13.5-1] 3 commits: Add purple tor version of the loading APNG.

richard (@richard) git at gitlab.torproject.org
Wed Apr 17 20:56:31 UTC 2024



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


Commits:
e992fb9d by Henry Wilkes at 2024-04-17T20:39:07+00:00
Add purple tor version of the loading APNG.

- - - - -
3cd48ad5 by Henry Wilkes at 2024-04-17T20:39:07+00:00
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection

Bug 40843: Add a "testing" state to the internet status.

Bug 41383: Improved accessibility of the network status areas by
managing focus and making the status an aria-live area so that changes
are announced without requiring focus.

Also re-arranged the markup to ensure the "Connection" overview and
network status areas are not part of search results.

Also moved the "Tor network" status onto a new line:

1. This improved navigation with a screen reader.
2. Ensures that the "Tor network" status does not jump horizontally
   during a test.
3. Ensures that each status area does not take up too much horizontal
   space so that the minimum page width can reduce. E.g. the "Tor
   network" area regularly took up more than 50% of the available width,
   and these may be wider depending on the locale.

- - - - -
d1c78659 by Henry Wilkes at 2024-04-17T20:39:07+00:00
fixup! Tor Browser strings

Bug 42457: Add a "testing" state to the internet status area.

- - - - -


10 changed files:

- browser/components/torpreferences/content/connectionPane.js
- browser/components/torpreferences/content/connectionPane.xhtml
- browser/components/torpreferences/content/torPreferences.css
- browser/locales/en-US/browser/tor-browser.ftl
- toolkit/themes/shared/desktop-jar.inc.mn
- + toolkit/themes/shared/icons/tor-dark-loading.png
- + toolkit/themes/shared/icons/tor-dark-loading at 2x.png
- + toolkit/themes/shared/icons/tor-light-loading.png
- + toolkit/themes/shared/icons/tor-light-loading at 2x.png
- + tools/torbrowser/generate_tor_loading_png.py


Changes:

=====================================
browser/components/torpreferences/content/connectionPane.js
=====================================
@@ -5,7 +5,8 @@
 
 "use strict";
 
-/* global Services, gSubDialog */
+/* import-globals-from /browser/components/preferences/preferences.js */
+/* import-globals-from /browser/components/preferences/search.js */
 
 const { setTimeout, clearTimeout } = ChromeUtils.import(
   "resource://gre/modules/Timer.jsm"
@@ -90,12 +91,6 @@ const Lox = {
 };
 */
 
-const InternetStatus = Object.freeze({
-  Unknown: 0,
-  Online: 1,
-  Offline: -1,
-});
-
 /**
  * Make changes to TorSettings and save them.
  *
@@ -2283,6 +2278,168 @@ const gBridgeSettings = {
   },
 };
 
+/**
+ * Area to show the internet and tor network connection status.
+ */
+const gNetworkStatus = {
+  /**
+   * Initialize the area.
+   */
+  init() {
+    this._internetAreaEl = document.getElementById(
+      "network-status-internet-area"
+    );
+    this._internetResultEl = this._internetAreaEl.querySelector(
+      ".network-status-result"
+    );
+    this._internetTestButton = document.getElementById(
+      "network-status-internet-test-button"
+    );
+    this._internetTestButton.addEventListener("click", () => {
+      this._startInternetTest();
+    });
+
+    this._torAreaEl = document.getElementById("network-status-tor-area");
+    this._torResultEl = this._torAreaEl.querySelector(".network-status-result");
+    this._torConnectButton = document.getElementById(
+      "network-status-tor-connect-button"
+    );
+    this._torConnectButton.addEventListener("click", () => {
+      TorConnect.openTorConnect({ beginBootstrap: true });
+    });
+
+    this._updateInternetStatus("unknown");
+    this._updateTorConnectionStatus();
+
+    Services.obs.addObserver(this, TorConnectTopics.StateChange);
+  },
+
+  /**
+   * Un-initialize the area.
+   */
+  deinint() {
+    Services.obs.removeObserver(this, TorConnectTopics.StateChange);
+  },
+
+  observe(subject, topic, data) {
+    switch (topic) {
+      // triggered when tor connect state changes and we may
+      // need to update the messagebox
+      case TorConnectTopics.StateChange: {
+        this._updateTorConnectionStatus();
+        break;
+      }
+    }
+  },
+
+  /**
+   * Whether the test should be disabled.
+   *
+   * @type {boolean}
+   */
+  _internetTestDisabled: false,
+  /**
+   * Start the internet test.
+   */
+  async _startInternetTest() {
+    if (this._internetTestDisabled) {
+      return;
+    }
+    this._internetTestDisabled = true;
+    // We use "aria-disabled" rather than the "disabled" attribute so that the
+    // button can remain focusable during the test.
+    this._internetTestButton.setAttribute("aria-disabled", "true");
+    this._internetTestButton.classList.add("spoof-button-disabled");
+    try {
+      this._updateInternetStatus("testing");
+      const mrpc = new MoatRPC();
+      let status = null;
+      try {
+        await mrpc.init();
+        status = await mrpc.testInternetConnection();
+      } catch (err) {
+        console.log("Error while checking the Internet connection", err);
+      } finally {
+        mrpc.uninit();
+      }
+      if (status) {
+        this._updateInternetStatus(status.successful ? "online" : "offline");
+      } else {
+        this._updateInternetStatus("unknown");
+      }
+    } finally {
+      this._internetTestButton.removeAttribute("aria-disabled");
+      this._internetTestButton.classList.remove("spoof-button-disabled");
+      this._internetTestDisabled = false;
+    }
+  },
+
+  /**
+   * Update the shown internet status.
+   *
+   * @param {string} stateName - The name of the state to show.
+   */
+  _updateInternetStatus(stateName) {
+    let l10nId;
+    switch (stateName) {
+      case "testing":
+        l10nId = "tor-connection-internet-status-testing";
+        break;
+      case "offline":
+        l10nId = "tor-connection-internet-status-offline";
+        break;
+      case "online":
+        l10nId = "tor-connection-internet-status-online";
+        break;
+    }
+    if (l10nId) {
+      this._internetResultEl.setAttribute("data-l10n-id", l10nId);
+    } else {
+      this._internetResultEl.removeAttribute("data-l10n-id");
+      this._internetResultEl.textContent = "";
+    }
+
+    this._internetAreaEl.classList.toggle(
+      "status-loading",
+      stateName === "testing"
+    );
+    this._internetAreaEl.classList.toggle(
+      "status-offline",
+      stateName === "offline"
+    );
+  },
+
+  /**
+   * Update the shown Tor connection status.
+   */
+  _updateTorConnectionStatus() {
+    const buttonHadFocus = this._torConnectButton.contains(
+      document.activeElement
+    );
+    const isBootstrapped = TorConnect.state === TorConnectState.Bootstrapped;
+    const isBlocked = !isBootstrapped && TorConnect.potentiallyBlocked;
+    let l10nId;
+    if (isBootstrapped) {
+      l10nId = "tor-connection-network-status-connected";
+    } else if (isBlocked) {
+      l10nId = "tor-connection-network-status-blocked";
+    } else {
+      l10nId = "tor-connection-network-status-not-connected";
+    }
+
+    document.l10n.setAttributes(this._torResultEl, l10nId);
+    this._torAreaEl.classList.toggle("status-connected", isBootstrapped);
+    this._torAreaEl.classList.toggle("status-blocked", isBlocked);
+    if (isBootstrapped && buttonHadFocus) {
+      // Button has become hidden and will loose focus. Most likely this has
+      // happened because the user clicked the button to open about:torconnect.
+      // Since this is near the top of the page, we move focus to the search
+      // input (for when the user returns).
+      gSearchResultsPane.searchInput.focus();
+    }
+  },
+};
+
 /*
   Connection Pane
 
@@ -2304,8 +2461,6 @@ const gConnectionPane = (function () {
     // cached frequently accessed DOM elements
     _enableQuickstartCheckbox: null,
 
-    _internetStatus: InternetStatus.Unknown,
-
     // populate xul with strings and cache the relevant elements
     _populateXUL() {
       // saves tor settings to disk when navigate away from about:preferences
@@ -2321,80 +2476,6 @@ const gConnectionPane = (function () {
         }
       });
 
-      // Internet and Tor status
-      const internetStatus = document.getElementById(
-        "torPreferences-status-internet"
-      );
-      const internetResult = internetStatus.querySelector(
-        ".torPreferences-status-result"
-      );
-      const internetTest = document.getElementById(
-        "torPreferences-status-internet-test"
-      );
-      internetTest.addEventListener("click", () => {
-        this.onInternetTest();
-      });
-
-      const torConnectStatus = document.getElementById(
-        "torPreferences-status-tor-connect"
-      );
-      const torConnectResult = torConnectStatus.querySelector(
-        ".torPreferences-status-result"
-      );
-      const torConnectButton = document.getElementById(
-        "torPreferences-status-tor-connect-button"
-      );
-      torConnectButton.addEventListener("click", () => {
-        TorConnect.openTorConnect({ beginBootstrap: true });
-      });
-
-      this._populateStatus = () => {
-        let internetId;
-        switch (this._internetStatus) {
-          case InternetStatus.Online:
-            internetStatus.classList.remove("offline");
-            internetId = "tor-connection-internet-status-online";
-            break;
-          case InternetStatus.Offline:
-            internetStatus.classList.add("offline");
-            internetId = "tor-connection-internet-status-offline";
-            break;
-          case InternetStatus.Unknown:
-          default:
-            internetStatus.classList.remove("offline");
-            break;
-        }
-        if (internetId) {
-          document.l10n.setAttributes(internetResult, internetId);
-          internetResult.hidden = false;
-        } else {
-          internetResult.hidden = true;
-        }
-
-        let connectId;
-        // FIXME: What about the TorConnectState.Disabled state?
-        if (TorConnect.state === TorConnectState.Bootstrapped) {
-          torConnectStatus.classList.add("connected");
-          torConnectStatus.classList.remove("blocked");
-          connectId = "tor-connection-network-status-connected";
-          // NOTE: If the button is focused when we hide it, the focus may be
-          // lost. But we don't have an obvious place to put the focus instead.
-          torConnectButton.hidden = true;
-        } else {
-          torConnectStatus.classList.remove("connected");
-          torConnectStatus.classList.toggle(
-            "blocked",
-            TorConnect.potentiallyBlocked
-          );
-          connectId = TorConnect.potentiallyBlocked
-            ? "tor-connection-network-status-blocked"
-            : "tor-connection-network-status-not-connected";
-          torConnectButton.hidden = false;
-        }
-        document.l10n.setAttributes(torConnectResult, connectId);
-      };
-      this._populateStatus();
-
       // Quickstart
       this._enableQuickstartCheckbox = document.getElementById(
         "torPreferences-quickstart-toggle"
@@ -2514,6 +2595,7 @@ const gConnectionPane = (function () {
 
     init() {
       gBridgeSettings.init();
+      gNetworkStatus.init();
 
       TorSettings.initializedPromise.then(() => this._populateXUL());
 
@@ -2526,6 +2608,7 @@ const gConnectionPane = (function () {
 
     uninit() {
       gBridgeSettings.uninit();
+      gNetworkStatus.uninit();
 
       // unregister our observer topics
       Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
@@ -2554,36 +2637,12 @@ const gConnectionPane = (function () {
         // triggered when tor connect state changes and we may
         // need to update the messagebox
         case TorConnectTopics.StateChange: {
-          this.onStateChange();
+          this._showAutoconfiguration();
           break;
         }
       }
     },
 
-    async onInternetTest() {
-      const mrpc = new MoatRPC();
-      let status = null;
-      try {
-        await mrpc.init();
-        status = await mrpc.testInternetConnection();
-      } catch (err) {
-        console.log("Error while checking the Internet connection", err);
-      } finally {
-        mrpc.uninit();
-      }
-      if (status) {
-        this._internetStatus = status.successful
-          ? InternetStatus.Online
-          : InternetStatus.Offline;
-        this._populateStatus();
-      }
-    },
-
-    onStateChange() {
-      this._populateStatus();
-      this._showAutoconfiguration();
-    },
-
     onAdvancedSettings() {
       gSubDialog.open(
         "chrome://browser/content/torpreferences/connectionSettingsDialog.xhtml",


=====================================
browser/components/torpreferences/content/connectionPane.xhtml
=====================================
@@ -5,16 +5,13 @@
   src="chrome://browser/content/torpreferences/connectionPane.js"
 />
 <html:template id="template-paneConnection">
-  <hbox
+  <vbox
     id="torPreferencesCategory"
     class="subcategory"
     data-category="paneConnection"
     hidden="true"
   >
     <html:h1 data-l10n-id="tor-connection-settings-heading"></html:h1>
-  </hbox>
-
-  <groupbox data-category="paneConnection" hidden="true">
     <description flex="1">
       <html:span
         data-l10n-id="tor-connection-overview"
@@ -28,46 +25,61 @@
         data-l10n-id="tor-connection-browser-learn-more-link"
       />
     </description>
-  </groupbox>
-
-  <groupbox
-    id="torPreferences-status-group"
-    data-category="paneConnection"
-    hidden="true"
-  >
-    <hbox id="torPreferences-status-box">
-      <hbox
-        id="torPreferences-status-internet"
-        class="torPreferences-status-grouping"
+    <!-- Keep within #torPreferencesCategory so this won't appear in search
+       - results. -->
+    <html:div
+      id="network-status-internet-area"
+      class="network-status-area"
+      role="group"
+      aria-labelledby="network-status-internet-area-label"
+    >
+      <html:img alt="" class="network-status-icon" />
+      <!-- Use an aria-live area to announce "Internet: Online" or
+         - "Internet: Offline". We only expect this to change when triggered by
+         - the user clicking the "Test" button, so shouldn't occur unexpectedly.
+         -->
+      <html:div
+        class="network-status-live-area"
+        aria-live="polite"
+        aria-atomic="true"
       >
-        <image class="torPreferences-status-icon" />
         <html:span
-          class="torPreferences-status-name"
+          id="network-status-internet-area-label"
+          class="network-status-label"
           data-l10n-id="tor-connection-internet-status-label"
         ></html:span>
-        <html:span class="torPreferences-status-result"></html:span>
-        <html:button
-          id="torPreferences-status-internet-test"
-          data-l10n-id="tor-connection-internet-status-test-button"
-        ></html:button>
-      </hbox>
-      <hbox
-        id="torPreferences-status-tor-connect"
-        class="torPreferences-status-grouping"
-      >
-        <image class="torPreferences-status-icon" />
-        <html:span
-          class="torPreferences-status-name"
-          data-l10n-id="tor-connection-network-status-label"
-        ></html:span>
-        <html:span class="torPreferences-status-result"></html:span>
-        <html:button
-          id="torPreferences-status-tor-connect-button"
-          data-l10n-id="tor-connection-network-status-connect-button"
-        ></html:button>
-      </hbox>
-    </hbox>
-  </groupbox>
+        <img alt="" class="network-status-loading-icon" />
+        <html:span class="network-status-result"></html:span>
+      </html:div>
+      <html:button
+        id="network-status-internet-test-button"
+        data-l10n-id="tor-connection-internet-status-test-button"
+      ></html:button>
+    </html:div>
+    <html:div
+      id="network-status-tor-area"
+      class="network-status-area"
+      role="group"
+      aria-labelledby="network-status-tor-area-label"
+    >
+      <html:img alt="" class="network-status-icon" />
+      <!-- NOTE: Unlike #network-status-internet-area, we do not wrap the label
+         - and status ("Tor network: Not connected", etc) in an aria-live area.
+         - This is not likely to change whilst this page has focus.
+         - Moreover, the status is already present in the torconnect status bar
+         - in the window tab bar. -->
+      <html:span
+        id="network-status-tor-area-label"
+        class="network-status-label"
+        data-l10n-id="tor-connection-network-status-label"
+      ></html:span>
+      <html:span class="network-status-result"></html:span>
+      <html:button
+        id="network-status-tor-connect-button"
+        data-l10n-id="tor-connection-network-status-connect-button"
+      ></html:button>
+    </html:div>
+  </vbox>
 
   <!-- Quickstart -->
   <groupbox data-category="paneConnection" hidden="true">


=====================================
browser/components/torpreferences/content/torPreferences.css
=====================================
@@ -5,25 +5,36 @@
   list-style-image: url("chrome://global/content/torconnect/tor-connect.svg");
 }
 
+/* Make a button appear disabled, whilst still allowing it to keep keyboard
+ * focus. */
+button.spoof-button-disabled {
+  /* Borrow the :disabled rule from common-shared.css */
+  opacity: 0.4;
+  /* Also ensure it does not get hover or active styling. */
+  pointer-events: none;
+}
+
 /* Status */
 
-#torPreferences-status-box {
-  display: flex;
-  align-items: center;
-  gap: 32px;
+#network-status-internet-area {
+  margin-block: 16px;
+}
+
+#network-status-tor-area {
+  margin-block: 0 32px;
 }
 
-.torPreferences-status-grouping {
+.network-status-area {
   display: flex;
   align-items: center;
   white-space: nowrap;
 }
 
-.torPreferences-status-grouping > * {
+.network-status-area > * {
   flex: 0 0 auto;
 }
 
-.torPreferences-status-icon {
+.network-status-icon {
   width: 18px;
   height: 18px;
   margin-inline-end: 8px;
@@ -32,42 +43,74 @@
   stroke: currentColor;
 }
 
-#torPreferences-status-internet .torPreferences-status-icon {
-  list-style-image: url("chrome://browser/content/torpreferences/network.svg");
+#network-status-internet-area .network-status-icon {
+  content: url("chrome://browser/content/torpreferences/network.svg");
 }
 
-#torPreferences-status-tor-connect .torPreferences-status-icon {
-  list-style-image: url("chrome://global/content/torconnect/tor-connect-broken.svg");
+#network-status-internet-area.status-offline .network-status-icon {
+  content: url("chrome://browser/content/torpreferences/network-broken.svg");
 }
 
-.torPreferences-status-name {
-  font-weight: bold;
-  margin-inline-end: 0.75em;
+#network-status-tor-area .network-status-icon {
+  content: url("chrome://global/content/torconnect/tor-connect.svg");
 }
 
-.torPreferences-status-result {
-  margin-inline-end: 8px;
+#network-status-tor-area:not(.status-connected) .network-status-icon {
+  content: url("chrome://global/content/torconnect/tor-connect-broken.svg");
 }
 
-#torPreferences-status-internet.offline .torPreferences-status-icon {
-  list-style-image: url("chrome://browser/content/torpreferences/network-broken.svg");
+#network-status-tor-area.status-blocked .network-status-icon {
+  /* Same as .tor-connect-status-potentially-blocked. */
+  stroke: #c50042;
 }
 
-#torPreferences-status-tor-connect.connected .torPreferences-status-icon {
-  list-style-image: url("chrome://global/content/torconnect/tor-connect.svg");
+ at media (prefers-color-scheme: dark) {
+  #network-status-tor-area.status-blocked  .network-status-icon {
+    stroke: #ff9aa2;
+  }
 }
 
-#torPreferences-status-tor-connect.blocked .torPreferences-status-icon {
-  /* Same as .tor-connect-status-potentially-blocked. */
-  stroke: #c50042;
+.network-status-live-area {
+  display: contents;
+}
+
+.network-status-label {
+  font-weight: bold;
+  margin-inline-end: 0.75em;
+}
+
+.network-status-loading-icon {
+  margin-inline-end: 0.5em;
+  width: 16px;
+  height: 16px;
+  content: image-set(
+    url("chrome://global/skin/icons/tor-light-loading.png"),
+    url("chrome://global/skin/icons/tor-light-loading@2x.png") 2x
+  );
 }
 
 @media (prefers-color-scheme: dark) {
-  #torPreferences-status-tor-connect.blocked .torPreferences-status-icon {
-    stroke: #ff9aa2;
+  .network-status-loading-icon {
+    content: image-set(
+      url("chrome://global/skin/icons/tor-dark-loading.png"),
+      url("chrome://global/skin/icons/tor-dark-loading@2x.png") 2x
+    );
   }
 }
 
+#network-status-internet-area:not(.status-loading) .network-status-loading-icon {
+  display: none;
+}
+
+.network-status-result:not(:empty) {
+  margin-inline-end: 0.75em;
+}
+
+#network-status-tor-area.status-connected #network-status-tor-connect-button {
+  /* Hide button when already connected. */
+  display: none;
+}
+
 /* Bridge settings */
 
 .tor-focusable-heading:focus-visible {


=====================================
browser/locales/en-US/browser/tor-browser.ftl
=====================================
@@ -65,6 +65,9 @@ tor-connection-internet-status-label = Internet:
 # Here "Test" is a verb, as in "test the internet connection".
 # Uses sentence case in English (US).
 tor-connection-internet-status-test-button = Test
+# Shown when testing the internet status.
+# Uses sentence case in English (US).
+tor-connection-internet-status-testing = Testing…
 # Shown when the user is connected to the internet.
 # Uses sentence case in English (US).
 tor-connection-internet-status-online = Online


=====================================
toolkit/themes/shared/desktop-jar.inc.mn
=====================================
@@ -86,6 +86,10 @@
   skin/classic/global/icons/link.svg                       (../../shared/icons/link.svg)
   skin/classic/global/icons/loading.png                    (../../shared/icons/loading.png)
   skin/classic/global/icons/loading at 2x.png                 (../../shared/icons/loading at 2x.png)
+  skin/classic/global/icons/tor-light-loading.png          (../../shared/icons/tor-light-loading.png)
+  skin/classic/global/icons/tor-light-loading at 2x.png       (../../shared/icons/tor-light-loading at 2x.png)
+  skin/classic/global/icons/tor-dark-loading.png           (../../shared/icons/tor-dark-loading.png)
+  skin/classic/global/icons/tor-dark-loading at 2x.png        (../../shared/icons/tor-dark-loading at 2x.png)
   skin/classic/global/icons/more.svg                       (../../shared/icons/more.svg)
   skin/classic/global/icons/open-in-new.svg                (../../shared/icons/open-in-new.svg)
   skin/classic/global/icons/page-portrait.svg              (../../shared/icons/page-portrait.svg)


=====================================
toolkit/themes/shared/icons/tor-dark-loading.png
=====================================
Binary files /dev/null and b/toolkit/themes/shared/icons/tor-dark-loading.png differ


=====================================
toolkit/themes/shared/icons/tor-dark-loading at 2x.png
=====================================
Binary files /dev/null and b/toolkit/themes/shared/icons/tor-dark-loading at 2x.png differ


=====================================
toolkit/themes/shared/icons/tor-light-loading.png
=====================================
Binary files /dev/null and b/toolkit/themes/shared/icons/tor-light-loading.png differ


=====================================
toolkit/themes/shared/icons/tor-light-loading at 2x.png
=====================================
Binary files /dev/null and b/toolkit/themes/shared/icons/tor-light-loading at 2x.png differ


=====================================
tools/torbrowser/generate_tor_loading_png.py
=====================================
@@ -0,0 +1,74 @@
+"""
+Script to convert the loading.png and loading at 2x.png blue spinners to purple
+spinners for Tor Browser, for both the light and dark themes.
+"""
+
+import argparse
+import colorsys
+import os
+
+from PIL import ExifTags, Image, ImageFilter
+
+parser = argparse.ArgumentParser(description="Convert the loading APNG to be purple.")
+parser.add_argument("loading_png", help="The loading png to convert")
+parser.add_argument(
+    "--light", required=True, help="The name of the light-theme purple output image"
+)
+parser.add_argument(
+    "--dark", required=True, help="The name of the dark-theme purple output image"
+)
+
+parsed_args = parser.parse_args()
+
+orig_im = Image.open(parsed_args.loading_png)
+
+
+def filter_to_light_theme(r, g, b):
+    h, s, v = colorsys.rgb_to_hsv(r, g, b)
+    # Convert from HSV 0.58, 1.0, 255 (start of the circle)
+    # to --purple-60 #8000d7 HSV 0.766, 1.0, 215
+    h = 0.766
+    v = v * 215 / 255
+    return colorsys.hsv_to_rgb(h, s, v)
+
+
+def filter_to_dark_theme(r, g, b):
+    h, s, v = colorsys.rgb_to_hsv(r, g, b)
+    # Convert from HSV 0.58, 1.0, 255 (start of the circle)
+    # to --purple-30 #c069ff HSV 0.766, 0.59, 255
+    h = 0.766
+    s = s * 0.59 / 1.0
+    return colorsys.hsv_to_rgb(h, s, v)
+
+
+filt_light = ImageFilter.Color3DLUT.generate(65, filter_to_light_theme)
+filt_dark = ImageFilter.Color3DLUT.generate(65, filter_to_dark_theme)
+
+transformed_light = []
+transformed_dark = []
+duration = orig_im.info["duration"]
+
+# Transform each APNG frame individually.
+for frame in range(orig_im.n_frames):
+    orig_im.seek(frame)
+    transformed_light.append(orig_im.filter(filt_light))
+    transformed_dark.append(orig_im.filter(filt_dark))
+
+exif = Image.Exif()
+exif[ExifTags.Base.ImageDescription] = f"Generated by {os.path.basename(__file__)}"
+
+transformed_light[0].save(
+    parsed_args.light,
+    save_all=True,
+    append_images=transformed_light[1:],
+    duration=duration,
+    exif=exif,
+)
+
+transformed_dark[0].save(
+    parsed_args.dark,
+    save_all=True,
+    append_images=transformed_dark[1:],
+    duration=duration,
+    exif=exif,
+)



View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/3cabcf78349bd85023829085d7ae23d79b059f66...d1c786595e9628f974f395c9eb79e50257bd71c4

-- 
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/3cabcf78349bd85023829085d7ae23d79b059f66...d1c786595e9628f974f395c9eb79e50257bd71c4
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/20240417/ba1c7497/attachment-0001.htm>


More information about the tbb-commits mailing list