test_BackupService_renderTemplate.js (10044B)
1 /* Any copyright is dedicated to the Public Domain. 2 https://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /** 7 * @typedef {object} TestRenderTemplateResult 8 * @property {string} markup 9 * The rendered template markup as a string. 10 * @property {Document} backupDOM 11 * The rendered template as parsed by a DOMParser. 12 */ 13 14 /** 15 * Renders a template and returns an object that contains both the raw markup 16 * and the DOM of the markup as parsed by DOMParser. 17 * 18 * @param {boolean} isEncrypted 19 * True if the template should report that the backup is encrypted. 20 * @param {object} metadata 21 * The metadata for the backup. See the BackupManifest schema for details. 22 * @returns {TestRenderTemplateResult} 23 */ 24 async function testRenderTemplate(isEncrypted, metadata = FAKE_METADATA) { 25 let bs = new BackupService(); 26 let markup = await bs.renderTemplate( 27 BackupService.ARCHIVE_TEMPLATE, 28 isEncrypted, 29 metadata 30 ); 31 let backupDOM = new DOMParser().parseFromString(markup, "text/html"); 32 return { backupDOM, markup }; 33 } 34 35 add_setup(() => { 36 // Setting this pref lets us use Cu.evalInSandbox to run the archive.js 37 // script. 38 Services.prefs.setBoolPref( 39 "security.allow_parent_unrestricted_js_loads", 40 true 41 ); 42 registerCleanupFunction(() => { 43 Services.prefs.clearUserPref("security.allow_parent_unrestricted_js_loads"); 44 }); 45 }); 46 47 /** 48 * Tests that header matches our expectations. The header of this file should 49 * be: 50 * 51 * <!DOCTYPE html> 52 * <!-- Version: 1 --> 53 */ 54 add_task(async function test_header() { 55 let { markup } = await testRenderTemplate(false /* isEncrypted */); 56 const EXPECTED_HEADER = 57 /^<!DOCTYPE html>[\r\n]+<!-- Version: (\d+) -->[\r\n]+/; 58 Assert.ok( 59 markup.match(EXPECTED_HEADER), 60 "Should have found the expected header." 61 ); 62 }); 63 64 /** 65 * Tests that the `encryption-state` DOM node says "Not encrypted" if the backup 66 * is not encrypted, and "Encrypted" otherwise. 67 */ 68 add_task(async function test_encryption_state() { 69 let { backupDOM } = await testRenderTemplate(false /* isEncrypted */); 70 Assert.equal( 71 backupDOM.querySelector("#encryption-state-value").textContent, 72 "No" 73 ); 74 75 ({ backupDOM } = await testRenderTemplate(true /* isEncrypted */)); 76 Assert.equal( 77 backupDOM.querySelector("#encryption-state-value").textContent, 78 "Yes" 79 ); 80 }); 81 82 /** 83 * Tests that metadata is properly inserted. The expected metadata inserted on 84 * the page is the time and date of the backup, as well as the name of the 85 * machine that the backup was created on. 86 */ 87 add_task(async function test_metadata() { 88 let { backupDOM } = await testRenderTemplate(true /* isEncrypted */); 89 let backupDate = new Date(FAKE_METADATA.date); 90 let expectedDate = new Intl.DateTimeFormat("en-US", { 91 dateStyle: "short", 92 }).format(backupDate); 93 let expectedTime = new Intl.DateTimeFormat("en-US", { 94 timeStyle: "short", 95 }).format(backupDate); 96 Assert.equal( 97 backupDOM.querySelector("#creation-date-value").textContent, 98 `${expectedTime}, ${expectedDate}` 99 ); 100 Assert.equal( 101 backupDOM.querySelector("#creation-device-value").textContent, 102 "A super cool machine" 103 ); 104 }); 105 106 /** 107 * Tests that metadata is properly escaped. This isn't exhaustive, since we're 108 * using Fluent under the hood, which is tested pretty widely already. 109 */ 110 add_task(async function test_hostile_metadata() { 111 let { backupDOM } = await testRenderTemplate(true /* isEncrypted */, { 112 date: "<script>alert('test');</script>", 113 appName: "<script>alert('test');</script>", 114 appVersion: "<script>alert('test');</script>", 115 buildID: "<script>alert('test');</script>", 116 profileName: "<script>alert('test');</script>", 117 machineName: "<script>alert('test');</script>", 118 osName: "<script>alert('test');</script>", 119 osVersion: "<script>alert('test');</script>", 120 legacyClientID: "<script>alert('test');</script>", 121 profileGroupID: "<script>alert('test');</script>", 122 accountID: "<script>alert('test');</script>", 123 accountEmail: "<script>alert('test');</script>", 124 }); 125 126 let scriptTags = backupDOM.querySelectorAll("script"); 127 Assert.equal( 128 scriptTags.length, 129 1, 130 "There should only be 1 script tag on the page." 131 ); 132 let scriptContent = scriptTags[0].innerHTML; 133 let evalSandbox = Cu.Sandbox(Cu.getGlobalForObject({})); 134 135 evalSandbox.navigator = { 136 userAgent: "", 137 }; 138 evalSandbox.document = { 139 getElementById: sinon.stub().callsFake((...args) => { 140 return backupDOM.getElementById(...args); 141 }), 142 body: { 143 toggleAttribute: sinon.stub(), 144 }, 145 location: { 146 pathname: "test_archive.html", 147 }, 148 }; 149 evalSandbox.alert = sinon.stub(); 150 151 Cu.evalInSandbox(scriptContent, evalSandbox); 152 153 Assert.ok(evalSandbox.alert.notCalled, "alert() was never called"); 154 }); 155 156 add_task(async function test_backup_file_path_from_javascript() { 157 let { backupDOM } = await testRenderTemplate(false /* isEncrypted */); 158 let scriptTags = backupDOM.querySelectorAll("script"); 159 Assert.equal( 160 scriptTags.length, 161 1, 162 "There should only be 1 script tag on the page." 163 ); 164 let scriptContent = scriptTags[0].innerHTML; 165 let evalSandbox = Cu.Sandbox(Cu.getGlobalForObject({})); 166 167 evalSandbox.navigator = { 168 userAgent: 169 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:129.0) Gecko/20100101 Firefox/129.0", 170 }; 171 evalSandbox.document = { 172 getElementById: sinon.stub().callsFake((...args) => { 173 return backupDOM.getElementById(...args); 174 }), 175 body: { 176 toggleAttribute: sinon.stub(), 177 }, 178 location: { 179 pathname: "test_archive.html", 180 }, 181 }; 182 183 Cu.evalInSandbox(scriptContent, evalSandbox); 184 185 Assert.equal( 186 backupDOM.querySelector("#backup-file-path-value").textContent, 187 "test_archive.html", 188 "backup file path should have been sourced from document.location" 189 ); 190 }); 191 192 /** 193 * Tests that if the User Agent is a browser that includes "Firefox", that 194 * toggleAttribute("is-moz-browser", true) is called on document.body. 195 */ 196 add_task(async function test_moz_browser_handling() { 197 let { backupDOM } = await testRenderTemplate(false /* isEncrypted */); 198 let scriptTags = backupDOM.querySelectorAll("script"); 199 Assert.equal( 200 scriptTags.length, 201 1, 202 "There should only be 1 script tag on the page." 203 ); 204 let scriptContent = scriptTags[0].innerHTML; 205 let evalSandbox = Cu.Sandbox(Cu.getGlobalForObject({})); 206 207 evalSandbox.navigator = { 208 userAgent: 209 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:129.0) Gecko/20100101 Firefox/129.0", 210 }; 211 evalSandbox.document = { 212 getElementById: sinon.stub().callsFake((...args) => { 213 return backupDOM.getElementById(...args); 214 }), 215 body: { 216 toggleAttribute: sinon.stub(), 217 }, 218 location: { 219 pathname: "test_archive.html", 220 }, 221 }; 222 223 Cu.evalInSandbox(scriptContent, evalSandbox); 224 225 Assert.ok( 226 evalSandbox.document.body.toggleAttribute.calledOnce, 227 "document.body.toggleAttribute called" 228 ); 229 Assert.ok( 230 evalSandbox.document.body.toggleAttribute.calledWith( 231 "is-moz-browser", 232 true 233 ), 234 "document.body.toggleAttribute called setting is-moz-browser to true" 235 ); 236 }); 237 238 /** 239 * Tests that if the User Agent is a browser that does not include "Firefox", 240 * that toggleAttribute("is-moz-browser", false) is called on document.body. 241 */ 242 add_task(async function test_non_moz_browser_handling() { 243 let { backupDOM } = await testRenderTemplate(true /* isEncrypted */); 244 let scriptTags = backupDOM.querySelectorAll("script"); 245 Assert.equal( 246 scriptTags.length, 247 1, 248 "There should only be 1 script tag on the page." 249 ); 250 let scriptContent = scriptTags[0].innerHTML; 251 let evalSandbox = Cu.Sandbox(Cu.getGlobalForObject({})); 252 253 evalSandbox.navigator = { 254 userAgent: 255 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15", 256 }; 257 evalSandbox.document = { 258 getElementById: sinon.stub().callsFake((...args) => { 259 return backupDOM.getElementById(...args); 260 }), 261 body: { 262 toggleAttribute: sinon.stub(), 263 }, 264 location: { 265 pathname: "test_archive.html", 266 }, 267 }; 268 269 Cu.evalInSandbox(scriptContent, evalSandbox); 270 271 Assert.ok( 272 evalSandbox.document.body.toggleAttribute.calledOnce, 273 "document.body.toggleAttribute called" 274 ); 275 Assert.ok( 276 evalSandbox.document.body.toggleAttribute.calledWith( 277 "is-moz-browser", 278 false 279 ), 280 "document.body.toggleAttribute called setting is-moz-browser to false" 281 ); 282 }); 283 284 /** 285 * Tests that the license header does not exist in the generated rendering. 286 */ 287 add_task(async function test_no_license() { 288 let { markup } = await testRenderTemplate(true /* isEncrypted */); 289 290 // Instead of looking for the exact license header (which might be indented) 291 // in such a way as to make string-searching brittle) we'll just look for 292 // a key part of the license header, which is the reference to 293 // https://mozilla.org/MPL. 294 295 Assert.ok( 296 !markup.includes("https://mozilla.org/MPL"), 297 "The license headers were stripped." 298 ); 299 }); 300 301 /** 302 * Tests that the "Learn More" support link/SUMO link has the expected UTM 303 * parameters. 304 */ 305 add_task(async function test_support_link_utm_parameters() { 306 let { backupDOM } = await testRenderTemplate(false /* isEncrypted */); 307 308 let supportLinkElement = backupDOM.getElementById("support-link"); 309 Assert.ok(supportLinkElement, "support link should be found"); 310 Assert.ok( 311 supportLinkElement.href, 312 "support link should have a non-empty href" 313 ); 314 315 let supportLinkUrl = new URL(supportLinkElement.href); 316 let { searchParams } = supportLinkUrl; 317 Assert.equal( 318 searchParams.get("utm_medium"), 319 "firefox-desktop", 320 "utm_medium should be firefox-desktop" 321 ); 322 Assert.equal( 323 searchParams.get("utm_source"), 324 "html-backup", 325 "utm_source should be html-backup" 326 ); 327 Assert.equal( 328 searchParams.get("utm_campaign"), 329 "fx-backup-restore", 330 "utm_campaign should be fx-backup-restore" 331 ); 332 });