tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 6c9c4294f1c5e8c1638e315c2dc92913615f6c3f
parent 59202fb317fdbb463d68416fc7a822c0e42995e3
Author: Tom Schuster <tschuster@mozilla.com>
Date:   Fri, 12 Dec 2025 07:56:27 +0000

Bug 1999468 - Display SVG favicons using the moz-remote-image: protocol (disabled by default). r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D273423

Diffstat:
Mbrowser/actors/LinkHandlerParent.sys.mjs | 4++--
Mbrowser/app/profile/firefox.js | 3+++
Mbrowser/base/content/test/favicons/browser.toml | 6++++++
Abrowser/base/content/test/favicons/browser_favicon_svg.js | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abrowser/base/content/test/favicons/file_favicon.svg | 3+++
Abrowser/base/content/test/favicons/file_favicon_svg.html | 12++++++++++++
Mbrowser/components/tabbrowser/content/tabbrowser.js | 23++++++++++++++++++++++-
Mbrowser/modules/FaviconUtils.sys.mjs | 10++++++++--
8 files changed, 114 insertions(+), 5 deletions(-)

diff --git a/browser/actors/LinkHandlerParent.sys.mjs b/browser/actors/LinkHandlerParent.sys.mjs @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { - TYPE_SVG, TYPE_ICO, + SVG_DATA_URI_PREFIX, TRUSTED_FAVICON_SCHEMES, blobAsDataURL, } from "moz-src:///browser/modules/FaviconUtils.sys.mjs"; @@ -224,7 +224,7 @@ export class LinkHandlerParent extends JSWindowActorParent { if ( !images && !TRUSTED_FAVICON_SCHEMES.includes(iconURI.scheme) && - !iconURL.startsWith(`data:${TYPE_SVG};base64,`) + !iconURL.startsWith(SVG_DATA_URI_PREFIX) ) { console.error( `Not allowed to set favicon "${iconURL}" with that scheme!` diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js @@ -2667,6 +2667,9 @@ pref("browser.tabs.fadeOutUnloadedTabs", false); // Whether tabs can be "split" or displayed side by side at once. pref("browser.tabs.splitView.enabled", false); +// Whether SVG favicons should be safely re-encoded using the moz-remote-image:// protocol. +pref("browser.tabs.remoteSVGIconDecoding", false); + // If true, unprivileged extensions may use experimental APIs on // nightly and developer edition. pref("extensions.experiments.enabled", false); diff --git a/browser/base/content/test/favicons/browser.toml b/browser/base/content/test/favicons/browser.toml @@ -91,6 +91,12 @@ support-files = [ "file_favicon.png^headers^", ] +["browser_favicon_svg.js"] +support-files = [ + "file_favicon_svg.html", + "file_favicon.svg" +] + ["browser_icon_discovery.js"] ["browser_invalid_href_fallback.js"] diff --git a/browser/base/content/test/favicons/browser_favicon_svg.js b/browser/base/content/test/favicons/browser_favicon_svg.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { ImageTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/ImageTestUtils.sys.mjs" +); + +const TEST_ROOT = + "http://mochi.test:8888/browser/browser/base/content/test/favicons/"; + +const PAGE_URL = TEST_ROOT + "file_favicon_svg.html"; +const SVG_URL = TEST_ROOT + "file_favicon.svg"; +const SVG_DATA_URL = `data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iZ3JlZW4iIC8+Cjwvc3ZnPgo=`; + +add_task(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["browser.tabs.remoteSVGIconDecoding", true]], + }); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: PAGE_URL, waitForLoad: false }, + async browser => { + await waitForFavicon(browser, SVG_URL); + is(browser.mIconURL, SVG_DATA_URL, "Got the SVG data URL"); + + let tabIconImg = gBrowser + .getTabForBrowser(browser) + .querySelector(".tab-icon-image"); + + let expectedParams = new URLSearchParams({ + url: SVG_DATA_URL, + width: 16, + height: 16, + }); + + is( + tabIconImg.src, + "moz-remote-image://?" + expectedParams, + "Image was loaded with the moz-remote-image: protocol" + ); + + if (!tabIconImg.complete) { + info("Awaiting tab-icon-image load"); + await new Promise(resolve => + tabIconImg.addEventListener("load", resolve, { once: true }) + ); + } + + let screenshotDataURL = TestUtils.screenshotArea(tabIconImg, window); + await ImageTestUtils.assertEqualImage( + window, + screenshotDataURL, + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHElEQVQ4T2NkaGD4z0ABYBw1YNSAUQPAYBgYAACDTRgBSE6IpwAAAABJRU5ErkJggg==", + "Got green favicon" + ); + } + ); +}); diff --git a/browser/base/content/test/favicons/file_favicon.svg b/browser/base/content/test/favicons/file_favicon.svg @@ -0,0 +1,3 @@ +<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> + <rect width="100" height="100" fill="green" /> +</svg> diff --git a/browser/base/content/test/favicons/file_favicon_svg.html b/browser/base/content/test/favicons/file_favicon_svg.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> + +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <link rel="icon" href="file_favicon.svg" type="image/svg+xml"> +</head> + +<body> +</body> + +</html> diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js @@ -119,6 +119,7 @@ TaskbarTabs: "resource:///modules/taskbartabs/TaskbarTabs.sys.mjs", UrlbarProviderOpenTabs: "moz-src:///browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs", + SVG_DATA_URI_PREFIX: "moz-src:///browser/modules/FaviconUtils.sys.mjs", }); ChromeUtils.defineLazyGetter(this, "tabLocalization", () => { return new Localization( @@ -184,6 +185,12 @@ "security.notification_enable_delay", 500 ); + XPCOMUtils.defineLazyPreferenceGetter( + this, + "_remoteSVGIconDecoding", + "browser.tabs.remoteSVGIconDecoding", + false + ); if (AppConstants.MOZ_CRASHREPORTER) { ChromeUtils.defineESModuleGetters(this, { @@ -1130,7 +1137,21 @@ aTab.removeAttribute("image"); } if (aIconURL) { - aTab.setAttribute("image", aIconURL); + let url = aIconURL; + if ( + this._remoteSVGIconDecoding && + url.startsWith(this.SVG_DATA_URI_PREFIX) + ) { + // 16px is hardcoded for .tab-icon-image in tabs.css + let size = Math.floor(16 * window.devicePixelRatio); + let params = new URLSearchParams({ + url, + width: size, + height: size, + }); + url = "moz-remote-image://?" + params; + } + aTab.setAttribute("image", url); } else { aTab.removeAttribute("image"); } diff --git a/browser/modules/FaviconUtils.sys.mjs b/browser/modules/FaviconUtils.sys.mjs @@ -2,8 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export const TYPE_ICO = "image/x-icon"; -export const TYPE_SVG = "image/svg+xml"; +const TYPE_ICO = "image/x-icon"; +const TYPE_SVG = "image/svg+xml"; + +export { TYPE_ICO, TYPE_SVG }; + +// SVG images are send as raw data URLs from the content process to the parent. +// The raw data: URL should NOT be used directly when displaying, but instead wrapped with a moz-remote-image: for safe re-encoding! +export const SVG_DATA_URI_PREFIX = `data:${TYPE_SVG};base64,`; // URL schemes that we don't want to load and convert to data URLs. export const TRUSTED_FAVICON_SCHEMES = Object.freeze([