browser_bug1393259.js (7276B)
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 file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 "use strict"; 5 6 /* 7 * This test validates that an OTF font installed in a directory not 8 * accessible to content processes is rendered correctly by checking that 9 * content displayed never uses the OS fallback font "LastResort". When 10 * a content process renders a page with the fallback font, that is an 11 * indication the content process failed to read or load the computed font. 12 * The test uses a version of the Fira Sans font and depends on the font 13 * not being already installed and enabled. 14 */ 15 16 const kPageURL = 17 "http://example.com/browser/security/sandbox/test/bug1393259.html"; 18 19 // Parameters for running the python script that registers/unregisters fonts. 20 let kPythonPath = "/usr/bin/python"; 21 if (AppConstants.isPlatformAndVersionAtLeast("macosx", 23.0)) { 22 kPythonPath = "/usr/local/bin/python3"; 23 } 24 const kFontInstallerPath = "browser/security/sandbox/test/mac_register_font.py"; 25 const kUninstallFlag = "-u"; 26 const kVerboseFlag = "-v"; 27 28 // Where to find the font in the test environment. 29 const kRepoFontPath = "browser/security/sandbox/test/FiraSans-Regular.otf"; 30 31 // Font name strings to check for. 32 const kLastResortFontName = "LastResort"; 33 const kTestFontName = "Fira Sans"; 34 35 // Home-relative path to install a private font. Where a private font is 36 // a font at a location not readable by content processes. 37 const kPrivateFontSubPath = "/FiraSans-Regular.otf"; 38 39 add_task(async function () { 40 await new Promise(resolve => waitForFocus(resolve, window)); 41 42 await BrowserTestUtils.withNewTab( 43 { 44 gBrowser, 45 url: kPageURL, 46 }, 47 async function (aBrowser) { 48 function runProcess(aCmd, aArgs, blocking = true) { 49 let cmdFile = Cc["@mozilla.org/file/local;1"].createInstance( 50 Ci.nsIFile 51 ); 52 cmdFile.initWithPath(aCmd); 53 54 let process = Cc["@mozilla.org/process/util;1"].createInstance( 55 Ci.nsIProcess 56 ); 57 process.init(cmdFile); 58 process.run(blocking, aArgs, aArgs.length); 59 return process.exitValue; 60 } 61 62 // Register the font at path |fontPath| and wait 63 // for the browser to detect the change. 64 async function registerFont(fontPath) { 65 let fontRegistered = getFontNotificationPromise(); 66 let exitCode = runProcess(kPythonPath, [ 67 kFontInstallerPath, 68 kVerboseFlag, 69 fontPath, 70 ]); 71 Assert.equal(exitCode, 0, "registering font" + fontPath); 72 if (exitCode == 0) { 73 // Wait for the font registration to be detected by the browser. 74 await fontRegistered; 75 } 76 } 77 78 // Unregister the font at path |fontPath|. If |waitForUnreg| is true, 79 // don't wait for the browser to detect the change and don't use 80 // the verbose arg for the unregister command. 81 async function unregisterFont(fontPath, waitForUnreg = true) { 82 let args = [kFontInstallerPath, kUninstallFlag]; 83 let fontUnregistered; 84 85 if (waitForUnreg) { 86 args.push(kVerboseFlag); 87 fontUnregistered = getFontNotificationPromise(); 88 } 89 90 let exitCode = runProcess(kPythonPath, args.concat(fontPath)); 91 if (waitForUnreg) { 92 Assert.equal(exitCode, 0, "unregistering font" + fontPath); 93 if (exitCode == 0) { 94 await fontUnregistered; 95 } 96 } 97 } 98 99 // Returns a promise that resolves when font info is changed. 100 let getFontNotificationPromise = () => 101 new Promise(resolve => { 102 const kTopic = "font-info-updated"; 103 function observe() { 104 Services.obs.removeObserver(observe, kTopic); 105 resolve(); 106 } 107 108 Services.obs.addObserver(observe, kTopic); 109 }); 110 111 let homeDir = Services.dirsvc.get("Home", Ci.nsIFile); 112 let privateFontPath = homeDir.path + kPrivateFontSubPath; 113 114 registerCleanupFunction(function () { 115 unregisterFont(privateFontPath, /* waitForUnreg = */ false); 116 runProcess("/bin/rm", [privateFontPath], /* blocking = */ false); 117 }); 118 119 // Copy the font file to the private path. 120 runProcess("/bin/cp", [kRepoFontPath, privateFontPath]); 121 122 // Cleanup previous aborted tests. 123 unregisterFont(privateFontPath, /* waitForUnreg = */ false); 124 125 // Get the original width, using the fallback monospaced font 126 let origWidth = await SpecialPowers.spawn( 127 aBrowser, 128 [], 129 async function () { 130 let window = content.window.wrappedJSObject; 131 let contentDiv = window.document.getElementById("content"); 132 return contentDiv.offsetWidth; 133 } 134 ); 135 136 // Activate the font we want to test at a non-standard path. 137 await registerFont(privateFontPath); 138 139 // Assign the new font to the content. 140 await SpecialPowers.spawn(aBrowser, [], async function () { 141 let window = content.window.wrappedJSObject; 142 let contentDiv = window.document.getElementById("content"); 143 contentDiv.style.fontFamily = "'Fira Sans', monospace"; 144 }); 145 146 // Wait until the width has changed, indicating the content process 147 // has recognized the newly-activated font. 148 while (true) { 149 let width = await SpecialPowers.spawn(aBrowser, [], async function () { 150 let window = content.window.wrappedJSObject; 151 let contentDiv = window.document.getElementById("content"); 152 return contentDiv.offsetWidth; 153 }); 154 if (width != origWidth) { 155 break; 156 } 157 // If the content wasn't ready yet, wait a little before re-checking. 158 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 159 await new Promise(c => setTimeout(c, 100)); 160 } 161 162 // Get a list of fonts now being used to display the web content. 163 let fontList = await SpecialPowers.spawn(aBrowser, [], async function () { 164 let window = content.window.wrappedJSObject; 165 let range = window.document.createRange(); 166 let contentDiv = window.document.getElementById("content"); 167 range.selectNode(contentDiv); 168 let fonts = InspectorUtils.getUsedFontFaces(range); 169 170 let fontList = []; 171 for (let i = 0; i < fonts.length; i++) { 172 fontList.push({ name: fonts[i].name }); 173 } 174 return fontList; 175 }); 176 177 let lastResortFontUsed = false; 178 let testFontUsed = false; 179 180 for (let font of fontList) { 181 // Did we fall back to the "LastResort" font? 182 if (!lastResortFontUsed && font.name.includes(kLastResortFontName)) { 183 lastResortFontUsed = true; 184 continue; 185 } 186 // Did we render using our test font as expected? 187 if (!testFontUsed && font.name.includes(kTestFontName)) { 188 testFontUsed = true; 189 continue; 190 } 191 } 192 193 Assert.ok( 194 !lastResortFontUsed, 195 `The ${kLastResortFontName} fallback font was not used` 196 ); 197 198 Assert.ok(testFontUsed, `The test font "${kTestFontName}" was used`); 199 200 await unregisterFont(privateFontPath); 201 } 202 ); 203 });