tor-browser

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

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 });