QRCodeGenerator.sys.mjs (4596B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * QR Code Generator with Firefox logo overlay 7 * This module generates QR codes with the Firefox logo in the center 8 * Uses a worker thread for QR generation to avoid blocking the main thread 9 */ 10 11 const lazy = {}; 12 13 ChromeUtils.defineLazyGetter(lazy, "logConsole", function () { 14 return console.createInstance({ 15 prefix: "QRCodeGenerator", 16 maxLogLevel: Services.prefs.getBoolPref("browser.qrcode.log", false) 17 ? "Debug" 18 : "Warn", 19 }); 20 }); 21 22 ChromeUtils.defineESModuleGetters(lazy, { 23 QRCodeWorker: "moz-src:///browser/components/qrcode/QRCodeWorker.sys.mjs", 24 }); 25 26 export const QRCodeGenerator = { 27 /** 28 * Generate a QR code for the given URL with Firefox logo overlay 29 * 30 * @param {string} url - The URL to encode 31 * @param {Document} document - The document to use for creating elements 32 * @returns {Promise<string>} - Data URI of the QR code with logo 33 */ 34 async generateQRCode(url, document) { 35 // Create a fresh worker for this generation 36 // Worker will be terminated after use to free resources 37 const worker = new lazy.QRCodeWorker(); 38 39 try { 40 // Generate the base QR code with high error correction to allow for logo overlay 41 // Use worker thread to avoid blocking main thread 42 const qrData = await worker.generateQRCode(url, "H"); 43 44 // Use a higher resolution for better quality (scale up 4x) 45 const scale = 4; 46 const canvas = document.createElementNS( 47 "http://www.w3.org/1999/xhtml", 48 "canvas" 49 ); 50 canvas.width = qrData.width * scale; 51 canvas.height = qrData.height * scale; 52 const ctx = canvas.getContext("2d"); 53 54 // Disable image smoothing for crisp QR code rendering 55 ctx.imageSmoothingEnabled = false; 56 57 // Load and draw the base QR code at high resolution 58 const qrImage = await this._loadImage(document, qrData.src); 59 ctx.drawImage(qrImage, 0, 0, qrData.width * scale, qrData.height * scale); 60 61 // Calculate logo size and position (center of QR code) 62 // Use 18% of QR code size (reduced from 25%) to stay within error correction limits 63 const logoSize = Math.floor(qrData.width * 0.18) * scale; 64 const centerX = Math.floor((qrData.width * scale) / 2); 65 const centerY = Math.floor((qrData.height * scale) / 2); 66 67 // Draw circular white background for logo with minimal padding 68 const padding = 4 * scale; 69 const radius = (logoSize + padding * 2) / 2; 70 ctx.fillStyle = "white"; 71 ctx.beginPath(); 72 ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); 73 ctx.fill(); 74 75 // Load and draw the Firefox logo at high resolution 76 try { 77 const logoImage = await this._loadFirefoxLogo(document); 78 // Re-enable smoothing for the logo to avoid pixelation 79 ctx.imageSmoothingEnabled = true; 80 ctx.imageSmoothingQuality = "high"; 81 82 // Draw logo centered 83 const logoX = centerX - logoSize / 2; 84 const logoY = centerY - logoSize / 2; 85 ctx.drawImage(logoImage, logoX, logoY, logoSize, logoSize); 86 } catch (e) { 87 lazy.logConsole.warn("Could not load Firefox logo for QR code:", e); 88 } 89 90 // Convert canvas to data URI 91 return canvas.toDataURL("image/png"); 92 } finally { 93 // Always terminate the worker to free resources 94 try { 95 await worker.terminate(); 96 lazy.logConsole.debug("QRCode worker terminated successfully"); 97 } catch (e) { 98 lazy.logConsole.warn("Failed to terminate QRCode worker:", e); 99 } 100 } 101 }, 102 103 /** 104 * Load an image from a URL/data URI 105 * 106 * @param {Document} document - The document to use for creating the image 107 * @param {string} src - The image source 108 * @returns {Promise<HTMLImageElement>} 109 */ 110 _loadImage(document, src) { 111 return new Promise((resolve, reject) => { 112 const img = document.createElementNS( 113 "http://www.w3.org/1999/xhtml", 114 "img" 115 ); 116 img.onload = () => resolve(img); 117 img.onerror = reject; 118 img.src = src; 119 }); 120 }, 121 122 /** 123 * Load the Firefox logo 124 * 125 * @param {Document} document - The document to use for creating the image 126 * @returns {Promise<HTMLImageElement>} 127 */ 128 async _loadFirefoxLogo(document) { 129 // Use the Firefox branding logo 130 return this._loadImage( 131 document, 132 "chrome://branding/content/about-logo.svg" 133 ); 134 }, 135 };