Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* 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";
/*
* This test validates that an OTF font installed in a directory not
* accessible to content processes is rendered correctly by checking that
* content displayed never uses the OS fallback font "LastResort". When
* a content process renders a page with the fallback font, that is an
* indication the content process failed to read or load the computed font.
* The test uses a version of the Fira Sans font and depends on the font
* not being already installed and enabled.
*/
const kPageURL =
// Parameters for running the python script that registers/unregisters fonts.
const kPythonPath = "/usr/bin/python";
const kFontInstallerPath = "browser/security/sandbox/test/mac_register_font.py";
const kUninstallFlag = "-u";
const kVerboseFlag = "-v";
// Where to find the font in the test environment.
const kRepoFontPath = "browser/security/sandbox/test/FiraSans-Regular.otf";
// Font name strings to check for.
const kLastResortFontName = "LastResort";
const kTestFontName = "Fira Sans";
// Home-relative path to install a private font. Where a private font is
// a font at a location not readable by content processes.
const kPrivateFontSubPath = "/FiraSans-Regular.otf";
add_task(async function () {
await new Promise(resolve => waitForFocus(resolve, window));
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: kPageURL,
},
async function (aBrowser) {
function runProcess(aCmd, aArgs, blocking = true) {
let cmdFile = Cc["@mozilla.org/file/local;1"].createInstance(
Ci.nsIFile
);
cmdFile.initWithPath(aCmd);
let process = Cc["@mozilla.org/process/util;1"].createInstance(
Ci.nsIProcess
);
process.init(cmdFile);
process.run(blocking, aArgs, aArgs.length);
return process.exitValue;
}
// Register the font at path |fontPath| and wait
// for the browser to detect the change.
async function registerFont(fontPath) {
let fontRegistered = getFontNotificationPromise();
let exitCode = runProcess(kPythonPath, [
kFontInstallerPath,
kVerboseFlag,
fontPath,
]);
Assert.ok(exitCode == 0, "registering font" + fontPath);
if (exitCode == 0) {
// Wait for the font registration to be detected by the browser.
await fontRegistered;
}
}
// Unregister the font at path |fontPath|. If |waitForUnreg| is true,
// don't wait for the browser to detect the change and don't use
// the verbose arg for the unregister command.
async function unregisterFont(fontPath, waitForUnreg = true) {
let args = [kFontInstallerPath, kUninstallFlag];
let fontUnregistered;
if (waitForUnreg) {
args.push(kVerboseFlag);
fontUnregistered = getFontNotificationPromise();
}
let exitCode = runProcess(kPythonPath, args.concat(fontPath));
if (waitForUnreg) {
Assert.ok(exitCode == 0, "unregistering font" + fontPath);
if (exitCode == 0) {
await fontUnregistered;
}
}
}
// Returns a promise that resolves when font info is changed.
let getFontNotificationPromise = () =>
new Promise(resolve => {
const kTopic = "font-info-updated";
function observe() {
Services.obs.removeObserver(observe, kTopic);
resolve();
}
Services.obs.addObserver(observe, kTopic);
});
let homeDir = Services.dirsvc.get("Home", Ci.nsIFile);
let privateFontPath = homeDir.path + kPrivateFontSubPath;
registerCleanupFunction(function () {
unregisterFont(privateFontPath, /* waitForUnreg = */ false);
runProcess("/bin/rm", [privateFontPath], /* blocking = */ false);
});
// Copy the font file to the private path.
runProcess("/bin/cp", [kRepoFontPath, privateFontPath]);
// Cleanup previous aborted tests.
unregisterFont(privateFontPath, /* waitForUnreg = */ false);
// Get the original width, using the fallback monospaced font
let origWidth = await SpecialPowers.spawn(
aBrowser,
[],
async function () {
let window = content.window.wrappedJSObject;
let contentDiv = window.document.getElementById("content");
return contentDiv.offsetWidth;
}
);
// Activate the font we want to test at a non-standard path.
await registerFont(privateFontPath);
// Assign the new font to the content.
await SpecialPowers.spawn(aBrowser, [], async function () {
let window = content.window.wrappedJSObject;
let contentDiv = window.document.getElementById("content");
contentDiv.style.fontFamily = "'Fira Sans', monospace";
});
// Wait until the width has changed, indicating the content process
// has recognized the newly-activated font.
while (true) {
let width = await SpecialPowers.spawn(aBrowser, [], async function () {
let window = content.window.wrappedJSObject;
let contentDiv = window.document.getElementById("content");
return contentDiv.offsetWidth;
});
if (width != origWidth) {
break;
}
// If the content wasn't ready yet, wait a little before re-checking.
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(c => setTimeout(c, 100));
}
// Get a list of fonts now being used to display the web content.
let fontList = await SpecialPowers.spawn(aBrowser, [], async function () {
let window = content.window.wrappedJSObject;
let range = window.document.createRange();
let contentDiv = window.document.getElementById("content");
range.selectNode(contentDiv);
let fonts = InspectorUtils.getUsedFontFaces(range);
let fontList = [];
for (let i = 0; i < fonts.length; i++) {
fontList.push({ name: fonts[i].name });
}
return fontList;
});
let lastResortFontUsed = false;
let testFontUsed = false;
for (let font of fontList) {
// Did we fall back to the "LastResort" font?
if (!lastResortFontUsed && font.name.includes(kLastResortFontName)) {
lastResortFontUsed = true;
continue;
}
// Did we render using our test font as expected?
if (!testFontUsed && font.name.includes(kTestFontName)) {
testFontUsed = true;
continue;
}
}
Assert.ok(
!lastResortFontUsed,
`The ${kLastResortFontName} fallback font was not used`
);
Assert.ok(testFontUsed, `The test font "${kTestFontName}" was used`);
await unregisterFont(privateFontPath);
}
);
});