tor-browser

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

commit a5a26cb1c8bdde42eb116ed6330e85f6b489140a
parent b00bde2b14b5e6565e8f8c85e994c3e91eef05cf
Author: Andrew Overholt <overholt@mozilla.com>
Date:   Fri,  9 Jan 2026 16:11:28 +0000

Bug 1996315 - Add QR code generation worker machinery r=mconley

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

Diffstat:
Mbrowser/base/content/test/static/browser_all_files_referenced.js | 8++++++++
Mbrowser/components/moz.build | 1+
Abrowser/components/qrcode/QRCodeGenerator.sys.mjs | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abrowser/components/qrcode/QRCodeWorker.sys.mjs | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abrowser/components/qrcode/QRCodeWorker.worker.mjs | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abrowser/components/qrcode/moz.build | 11+++++++++++
Abrowser/components/qrcode/test/xpcshell/test_QRCodeGenerator_worker_integration.js | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abrowser/components/qrcode/test/xpcshell/test_QRCodeWorker.js | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abrowser/components/qrcode/test/xpcshell/xpcshell.toml | 6++++++
Mtoolkit/components/qrcode/encoder.mjs | 18++++++++++--------
10 files changed, 489 insertions(+), 8 deletions(-)

diff --git a/browser/base/content/test/static/browser_all_files_referenced.js b/browser/base/content/test/static/browser_all_files_referenced.js @@ -328,6 +328,7 @@ var allowlist = [ { file: "resource://app/modules/backup/CookiesBackupResource.sys.mjs", }, + // Bug 2000945 - Move query intent detection to AI-window r?mardak (backed out due to unused file) { file: "moz-src:///browser/components/aiwindow/models/IntentClassifier.sys.mjs", @@ -346,6 +347,13 @@ var allowlist = [ { file: "moz-src:///browser/components/aiwindow/models/ConversationSuggestions.sys.mjs", }, + // Bug 1996315: QR code generation modules + { + file: "moz-src:///browser/components/qrcode/QRCodeGenerator.sys.mjs", + }, + { + file: "moz-src:///browser/components/qrcode/QRCodeWorker.sys.mjs", + }, ]; if (AppConstants.NIGHTLY_BUILD) { diff --git a/browser/components/moz.build b/browser/components/moz.build @@ -58,6 +58,7 @@ DIRS += [ "prompts", "protections", "protocolhandler", + "qrcode", "reportbrokensite", "resistfingerprinting", "screenshots", diff --git a/browser/components/qrcode/QRCodeGenerator.sys.mjs b/browser/components/qrcode/QRCodeGenerator.sys.mjs @@ -0,0 +1,135 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +/** + * QR Code Generator with Firefox logo overlay + * This module generates QR codes with the Firefox logo in the center + * Uses a worker thread for QR generation to avoid blocking the main thread + */ + +const lazy = {}; + +ChromeUtils.defineLazyGetter(lazy, "logConsole", function () { + return console.createInstance({ + prefix: "QRCodeGenerator", + maxLogLevel: Services.prefs.getBoolPref("browser.qrcode.log", false) + ? "Debug" + : "Warn", + }); +}); + +ChromeUtils.defineESModuleGetters(lazy, { + QRCodeWorker: "moz-src:///browser/components/qrcode/QRCodeWorker.sys.mjs", +}); + +export const QRCodeGenerator = { + /** + * Generate a QR code for the given URL with Firefox logo overlay + * + * @param {string} url - The URL to encode + * @param {Document} document - The document to use for creating elements + * @returns {Promise<string>} - Data URI of the QR code with logo + */ + async generateQRCode(url, document) { + // Create a fresh worker for this generation + // Worker will be terminated after use to free resources + const worker = new lazy.QRCodeWorker(); + + try { + // Generate the base QR code with high error correction to allow for logo overlay + // Use worker thread to avoid blocking main thread + const qrData = await worker.generateQRCode(url, "H"); + + // Use a higher resolution for better quality (scale up 4x) + const scale = 4; + const canvas = document.createElementNS( + "http://www.w3.org/1999/xhtml", + "canvas" + ); + canvas.width = qrData.width * scale; + canvas.height = qrData.height * scale; + const ctx = canvas.getContext("2d"); + + // Disable image smoothing for crisp QR code rendering + ctx.imageSmoothingEnabled = false; + + // Load and draw the base QR code at high resolution + const qrImage = await this._loadImage(document, qrData.src); + ctx.drawImage(qrImage, 0, 0, qrData.width * scale, qrData.height * scale); + + // Calculate logo size and position (center of QR code) + // Use 18% of QR code size (reduced from 25%) to stay within error correction limits + const logoSize = Math.floor(qrData.width * 0.18) * scale; + const centerX = Math.floor((qrData.width * scale) / 2); + const centerY = Math.floor((qrData.height * scale) / 2); + + // Draw circular white background for logo with minimal padding + const padding = 4 * scale; + const radius = (logoSize + padding * 2) / 2; + ctx.fillStyle = "white"; + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); + ctx.fill(); + + // Load and draw the Firefox logo at high resolution + try { + const logoImage = await this._loadFirefoxLogo(document); + // Re-enable smoothing for the logo to avoid pixelation + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = "high"; + + // Draw logo centered + const logoX = centerX - logoSize / 2; + const logoY = centerY - logoSize / 2; + ctx.drawImage(logoImage, logoX, logoY, logoSize, logoSize); + } catch (e) { + lazy.logConsole.warn("Could not load Firefox logo for QR code:", e); + } + + // Convert canvas to data URI + return canvas.toDataURL("image/png"); + } finally { + // Always terminate the worker to free resources + try { + await worker.terminate(); + lazy.logConsole.debug("QRCode worker terminated successfully"); + } catch (e) { + lazy.logConsole.warn("Failed to terminate QRCode worker:", e); + } + } + }, + + /** + * Load an image from a URL/data URI + * + * @param {Document} document - The document to use for creating the image + * @param {string} src - The image source + * @returns {Promise<HTMLImageElement>} + */ + _loadImage(document, src) { + return new Promise((resolve, reject) => { + const img = document.createElementNS( + "http://www.w3.org/1999/xhtml", + "img" + ); + img.onload = () => resolve(img); + img.onerror = reject; + img.src = src; + }); + }, + + /** + * Load the Firefox logo + * + * @param {Document} document - The document to use for creating the image + * @returns {Promise<HTMLImageElement>} + */ + async _loadFirefoxLogo(document) { + // Use the Firefox branding logo + return this._loadImage( + document, + "chrome://branding/content/about-logo.svg" + ); + }, +}; diff --git a/browser/components/qrcode/QRCodeWorker.sys.mjs b/browser/components/qrcode/QRCodeWorker.sys.mjs @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +/** + * QRCodeWorker - Worker-based QR code generation + * + * This module provides a worker-based implementation for QR code generation + * to avoid blocking the main thread during QR code processing. + */ + +import { BasePromiseWorker } from "resource://gre/modules/PromiseWorker.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineLazyGetter(lazy, "logConsole", function () { + return console.createInstance({ + prefix: "QRCodeWorker", + maxLogLevel: Services.prefs.getBoolPref("browser.qrcode.log", false) + ? "Debug" + : "Warn", + }); +}); + +/** + * Worker wrapper for QR code generation + */ +export class QRCodeWorker extends BasePromiseWorker { + constructor() { + super("moz-src:///browser/components/qrcode/QRCodeWorker.worker.mjs", { + type: "module", + }); + + // Set up logging + this.log = (...args) => lazy.logConsole.debug(...args); + } + + /** + * Simple ping test for worker communication + * + * @returns {Promise<string>} Returns "pong" + */ + async ping() { + return this.post("ping", []); + } + + /** + * Check if the QRCode library is available in the worker + * + * @returns {Promise<boolean>} True if library is available + */ + async hasQRCodeLibrary() { + return this.post("hasQRCodeLibrary", []); + } + + /** + * Generate a QR code for the given URL + * + * @param {string} url - The URL to encode in the QR code + * @param {string} errorCorrectionLevel - Error correction level (L, M, Q, H) + * @returns {Promise<object>} Object with width, height, and src data URI + */ + async generateQRCode(url, errorCorrectionLevel = "H") { + return this.post("generateQRCode", [url, errorCorrectionLevel]); + } + + /** + * Terminate the worker and clean up resources + */ + async terminate() { + super.terminate(); + } +} diff --git a/browser/components/qrcode/QRCodeWorker.worker.mjs b/browser/components/qrcode/QRCodeWorker.worker.mjs @@ -0,0 +1,90 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +// Now load the QRCode library with the full resource URI +import { QR } from "moz-src:///toolkit/components/qrcode/encoder.mjs"; +import { PromiseWorker } from "resource://gre/modules/workers/PromiseWorker.mjs"; + +/** + * QRCode Worker Implementation + * + * This worker handles QR code generation off the main thread. + */ + +/** + * The QR Code generator that runs in a worker thread + */ +class QRCodeWorkerImpl { + constructor() { + this.#connectToPromiseWorker(); + } + + /** + * Simple ping test for worker communication + * + * @returns {string} Returns "pong" + */ + ping() { + return "pong"; + } + + /** + * Check if the QRCode library is available + * + * @returns {boolean} True if library is loaded + */ + hasQRCodeLibrary() { + return typeof QR !== "undefined" && QR !== null; + } + + /** + * Generate a QR code for the given URL + * + * @param {string} url - The URL to encode + * @param {string} errorCorrectionLevel - Error correction level (L, M, Q, H) + * @returns {object} Object with width, height, and src data URI + */ + generateQRCode(url, errorCorrectionLevel = "H") { + if (!QR || !QR.encodeToDataURI) { + throw new Error("QRCode library not available in worker"); + } + + // Generate the QR code data URI + const qrData = QR.encodeToDataURI(url, errorCorrectionLevel); + + return { + width: qrData.width, + height: qrData.height, + src: qrData.src, + }; + } + + /** + * Glue code to connect the `QRCodeWorkerImpl` to the PromiseWorker interface. + */ + #connectToPromiseWorker() { + const worker = new PromiseWorker.AbstractWorker(); + + worker.dispatch = (method, args = []) => { + if (!this[method]) { + throw new Error("Method does not exist: " + method); + } + return this[method](...args); + }; + + worker.close = () => self.close(); + + worker.postMessage = (message, ...transfers) => { + self.postMessage(message, ...transfers); + }; + + self.addEventListener("message", msg => worker.handleMessage(msg)); + self.addEventListener("unhandledrejection", function (error) { + throw error.reason; + }); + } +} + +// Create the worker instance +new QRCodeWorkerImpl(); diff --git a/browser/components/qrcode/moz.build b/browser/components/qrcode/moz.build @@ -0,0 +1,11 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +MOZ_SRC_FILES += [ + "QRCodeGenerator.sys.mjs", + "QRCodeWorker.sys.mjs", + "QRCodeWorker.worker.mjs", +] + +XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell/xpcshell.toml"] diff --git a/browser/components/qrcode/test/xpcshell/test_QRCodeGenerator_worker_integration.js b/browser/components/qrcode/test/xpcshell/test_QRCodeGenerator_worker_integration.js @@ -0,0 +1,88 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +"use strict"; + +/** + * Test QRCodeGenerator with worker integration + */ + +const { QRCodeGenerator } = ChromeUtils.importESModule( + "moz-src:///browser/components/qrcode/QRCodeGenerator.sys.mjs" +); + +/** + * Helper function to create a minimal mock document with required methods + */ +function createMockDocument() { + return { + createElementNS: (ns, tagName) => { + if (tagName === "canvas") { + return { + width: 0, + height: 0, + getContext: () => ({ + imageSmoothingEnabled: false, + imageSmoothingQuality: "high", + fillStyle: "white", + beginPath: () => {}, + arc: () => {}, + fill: () => {}, + drawImage: () => {}, + }), + toDataURL: () => "data:image/png;base64,mock", + }; + } else if (tagName === "img") { + return { + onload: null, + onerror: null, + set src(value) { + // Simulate image load + Services.tm.dispatchToMainThread(() => { + if (this.onload) { + this.onload(); + } + }); + }, + }; + } + return {}; + }, + }; +} + +add_task(async function test_generator_uses_worker() { + info("Testing QRCodeGenerator generates QR code using worker"); + + const mockDocument = createMockDocument(); + + // Generate a QR code using the worker + const testUrl = "https://mozilla.org"; + const dataUri = await QRCodeGenerator.generateQRCode(testUrl, mockDocument); + + Assert.ok(dataUri, "Should get a data URI from generateQRCode"); + Assert.ok(dataUri.startsWith("data:image/"), "Should be a data URI"); + + // Worker is automatically cleaned up after generation +}); + +add_task(async function test_generator_multiple_generations() { + info("Testing QRCodeGenerator can generate multiple QR codes"); + + // Generate first QR code + const dataUri1 = await QRCodeGenerator.generateQRCode( + "https://mozilla.org", + createMockDocument() + ); + Assert.ok(dataUri1, "Should get first data URI"); + + // Generate second QR code + const dataUri2 = await QRCodeGenerator.generateQRCode( + "https://firefox.com", + createMockDocument() + ); + Assert.ok(dataUri2, "Should get second data URI"); + + // Each call creates and cleans up its own worker +}); diff --git a/browser/components/qrcode/test/xpcshell/test_QRCodeWorker.js b/browser/components/qrcode/test/xpcshell/test_QRCodeWorker.js @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +"use strict"; + +/** + * Test QRCodeWorker functionality + */ + +const { QRCodeWorker } = ChromeUtils.importESModule( + "moz-src:///browser/components/qrcode/QRCodeWorker.sys.mjs" +); + +add_task(async function test_worker_instantiation() { + info("Testing QRCodeWorker can be instantiated"); + + const worker = new QRCodeWorker(); + Assert.ok(worker, "QRCodeWorker instance should be created"); + + // Clean up + await worker.terminate(); +}); + +add_task(async function test_worker_responds_to_ping() { + info("Testing QRCodeWorker responds to ping message"); + const worker = new QRCodeWorker(); + + // Test ping functionality + const response = await worker.ping(); + Assert.equal(response, "pong", "Worker should respond with 'pong' to ping"); + + // Clean up + await worker.terminate(); +}); + +add_task(async function test_worker_can_load_qrcode_library() { + info("Testing QRCodeWorker can load QRCode library"); + + const worker = new QRCodeWorker(); + + // Test that the worker can check if the QRCode library is available + const hasLibrary = await worker.hasQRCodeLibrary(); + Assert.ok(hasLibrary, "Worker should have access to QRCode library"); + + // Clean up + await worker.terminate(); +}); + +add_task(async function test_worker_can_generate_simple_qrcode() { + info("Testing QRCodeWorker can generate a simple QR code"); + + const worker = new QRCodeWorker(); + + // Test generating a very simple QR code + const testUrl = "https://mozilla.org"; + const result = await worker.generateQRCode(testUrl); + + Assert.ok(result, "Should get a result from generateQRCode"); + Assert.ok(result.width, "Result should have a width"); + Assert.ok(result.height, "Result should have a height"); + Assert.ok(result.src, "Result should have a src data URI"); + Assert.ok(result.src.startsWith("data:image/"), "src should be a data URI"); + + // Clean up + await worker.terminate(); +}); diff --git a/browser/components/qrcode/test/xpcshell/xpcshell.toml b/browser/components/qrcode/test/xpcshell/xpcshell.toml @@ -0,0 +1,6 @@ +[DEFAULT] +firefox-appdir = "browser" + +["test_QRCodeGenerator_worker_integration.js"] + +["test_QRCodeWorker.js"] diff --git a/toolkit/components/qrcode/encoder.mjs b/toolkit/components/qrcode/encoder.mjs @@ -2,15 +2,17 @@ * 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/. */ -const { XPCOMUtils } = ChromeUtils.importESModule( - "resource://gre/modules/XPCOMUtils.sys.mjs" -); +const lazy = {}; -const lazy = XPCOMUtils.declareLazy({ - qrcode: "moz-src:///third_party/js/qrcode/qrcode.mjs", - QRErrorCorrectionLevel: "moz-src:///third_party/js/qrcode/qrcode.mjs", - QRRSBlock: "moz-src:///third_party/js/qrcode/qrcode.mjs", -}); +ChromeUtils.defineESModuleGetters( + lazy, + { + qrcode: "moz-src:///third_party/js/qrcode/qrcode.mjs", + QRErrorCorrectionLevel: "moz-src:///third_party/js/qrcode/qrcode.mjs", + QRRSBlock: "moz-src:///third_party/js/qrcode/qrcode.mjs", + }, + { global: "current" } +); /** * There are many "versions" of QR codes, which describes how many dots appear