tor-browser

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

commit c06a945be3fc35232223b32c48484517a0a24752
parent 715d3b0dece00ac6c89ec879f13be049cb4b1713
Author: Erik Nordin <enordin@mozilla.com>
Date:   Wed,  3 Dec 2025 21:37:09 +0000

Bug 2003545 - Support Full-Page Translations for standalone SVGs r=translations-reviewers,gregtatum

This commit expands the context in which we add root elements to
the TranslationsDocument to support cases where the document may
not have a body. This happens in the case of a standalone SVG page.

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

Diffstat:
Mtoolkit/components/translations/content/translations-document.sys.mjs | 14++++++++++++--
Mtoolkit/components/translations/tests/browser/browser_translations_translation_document.js | 31+++++++++++++++++++++++++++++++
Mtoolkit/components/translations/tests/browser/shared-head.js | 18++++++++++++++----
3 files changed, 57 insertions(+), 6 deletions(-)

diff --git a/toolkit/components/translations/content/translations-document.sys.mjs b/toolkit/components/translations/content/translations-document.sys.mjs @@ -1590,6 +1590,10 @@ export class TranslationsDocument { this.#addRootElement(document.body); this.#addRootElement(document.head); this.#addRootElement(document.querySelector("title")); + if (!document.body && document.documentElement) { + // Handle documents such as standalone SVGs that lack a body. + this.#addRootElement(document.documentElement); + } ChromeUtils.addProfilerMarker( "TranslationsDocument Initialize", @@ -1624,7 +1628,13 @@ export class TranslationsDocument { } }; - if (document.body) { + if ( + // There exists a document body, so we are clear to continue. + document.body || + // The page has finished loading, but there is no document body. + // There may still be roots to add, such as in the case of a standalone SVG. + document.readyState !== "loading" + ) { addRootElements(); } else { // The TranslationsDocument was invoked before the DOM was ready, wait for @@ -2404,7 +2414,7 @@ export class TranslationsDocument { return; } - const element = asHTMLElement(node); + const element = asElement(node); if (!element) { return; } diff --git a/toolkit/components/translations/tests/browser/browser_translations_translation_document.js b/toolkit/components/translations/tests/browser/browser_translations_translation_document.js @@ -1004,6 +1004,37 @@ add_task(async function test_svgs_more() { await cleanup(); }); +add_task(async function test_standalone_svg_document() { + const svgSource = /* html */ ` + <svg xmlns="http://www.w3.org/2000/svg"> + <title>Test title</title> + <text x="10" y="20">Test text inside of standalone SVG.</text> + </svg> + `; + + const { translate, htmlMatches, cleanup, document } = + await createTranslationsDoc(svgSource, { parserType: "image/svg+xml" }); + + translate(); + + await htmlMatches( + "Standalone SVG documents are translated.", + /* html */ ` + <svg xmlns="http://www.w3.org/2000/svg"> + <title> + TEST TITLE + </title> + <text x="10" y="20"> + TEST TEXT INSIDE OF STANDALONE SVG. + </text> + </svg> + `, + document + ); + + await cleanup(); +}); + add_task(async function test_tables() { const { translate, htmlMatches, cleanup } = await createTranslationsDoc(/* html */ ` diff --git a/toolkit/components/translations/tests/browser/shared-head.js b/toolkit/components/translations/tests/browser/shared-head.js @@ -684,6 +684,7 @@ const { TranslationsDocument, LRUCache } = ChromeUtils.importESModule( * @param {object} [options] - Optional configuration. * @param {string} [options.sourceLanguage="en"] - Source language code (default: "en"). * @param {string} [options.targetLanguage="en"] - Target language code (default: "en"). + * @param {DOMParserSupportedType} [options.parserType="text/html"] - Parser type for the source content. * @param {(message: string) => Promise<string>} [options.mockedTranslatorPort] - Optional mock translation function. * @param {() => void} [options.mockedReportVisibleChange] - Optional callback for visibility reporting. * @returns {Promise<void>} Resolves when the document translation is complete. @@ -693,6 +694,7 @@ async function createTranslationsDoc( { sourceLanguage = "en", targetLanguage = "es", + parserType = "text/html", mockedTranslatorPort, mockedReportVisibleChange, } = {} @@ -706,12 +708,14 @@ async function createTranslationsDoc( }); const parser = new DOMParser(); - const document = parser.parseFromString(html, "text/html"); + const document = parser.parseFromString(html, parserType); // For some reason, the document <body> here from the DOMParser is "display: flex" by // default. Ensure that it is "display: block" instead, otherwise the children of the // <body> will not be "display: inline". - document.body.style.display = "block"; + if (document.body) { + document.body.style.display = "block"; + } let translationsDoc = null; @@ -823,6 +827,12 @@ async function createTranslationsDoc( let didSimulateIntersectionObservation = false; + const getHTMLSource = () => { + return ( + sourceDoc.body?.innerHTML ?? sourceDoc.documentElement?.outerHTML ?? "" + ); + }; + try { await waitForCondition(async () => { await waitForCondition( @@ -860,7 +870,7 @@ async function createTranslationsDoc( () => !translationsDoc.hasPendingCallbackOnEventLoop() ); - const actualHtml = naivelyPrettify(sourceDoc.body.innerHTML); + const actualHtml = naivelyPrettify(getHTMLSource()); const htmlMatches = expected.test(actualHtml); if (!htmlMatches && !didSimulateIntersectionObservation) { @@ -893,7 +903,7 @@ async function createTranslationsDoc( console.error(error); // Provide a nice error message. - const actual = naivelyPrettify(sourceDoc.body.innerHTML); + const actual = naivelyPrettify(getHTMLSource()); ok( false, `${message}\n\nExpected HTML:\n\n${