tor-browser

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

manifest.sys.mjs (29052B)


      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 import { globals } from "resource://reftest/globals.sys.mjs";
      6 
      7 const {
      8  NS_GFXINFO_CONTRACTID,
      9 
     10  TYPE_REFTEST_EQUAL,
     11  TYPE_REFTEST_NOTEQUAL,
     12  TYPE_LOAD,
     13  TYPE_SCRIPT,
     14  TYPE_PRINT,
     15 
     16  EXPECTED_PASS,
     17  EXPECTED_FAIL,
     18  EXPECTED_RANDOM,
     19  EXPECTED_FUZZY,
     20 
     21  PREF_BOOLEAN,
     22  PREF_STRING,
     23  PREF_INTEGER,
     24 
     25  FOCUS_FILTER_NEEDS_FOCUS_TESTS,
     26  FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS,
     27 
     28  g,
     29 } = globals;
     30 
     31 import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs";
     32 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
     33 
     34 const RE_PROTOCOL = /^\w+:/;
     35 const RE_PREF_ITEM = /^(|test-|ref-)pref\((.+?),(.*)\)$/;
     36 
     37 export function ReadTopManifest(aFileURL, aFilter, aManifestID) {
     38  var url = g.ioService.newURI(aFileURL);
     39  if (!url) {
     40    throw new Error("Expected a file or http URL for the manifest.");
     41  }
     42 
     43  g.manifestsLoaded = {};
     44  ReadManifest(url, aFilter, aManifestID);
     45 }
     46 
     47 // Note: If you materially change the reftest manifest parsing,
     48 // please keep the parser in layout/tools/reftest/__init__.py in sync.
     49 // (in particular keep CONDITIONS_JS_TO_MP in sync)
     50 // eslint-disable-next-line complexity
     51 function ReadManifest(aURL, aFilter, aManifestID) {
     52  // Ensure each manifest is only read once. This assumes that manifests that
     53  // are included with filters will be read via their include before they are
     54  // read directly in the case of a duplicate
     55  if (g.manifestsLoaded.hasOwnProperty(aURL.spec)) {
     56    if (g.manifestsLoaded[aURL.spec] === null) {
     57      return;
     58    }
     59    aFilter = [aFilter[0], aFilter[1], true];
     60  }
     61  g.manifestsLoaded[aURL.spec] = aFilter[1];
     62 
     63  var listURL = aURL;
     64  var channel = NetUtil.newChannel({
     65    uri: aURL,
     66    loadUsingSystemPrincipal: true,
     67  });
     68  try {
     69    var inputStream = channel.open();
     70  } catch (e) {
     71    g.logger.error("failed to open manifest at : " + aURL.spec);
     72    throw new Error(e);
     73  }
     74  if (channel instanceof Ci.nsIHttpChannel && channel.responseStatus != 200) {
     75    g.logger.error("HTTP ERROR : " + channel.responseStatus);
     76  }
     77  var streamBuf = getStreamContent(inputStream);
     78  inputStream.close();
     79  var lines = streamBuf.split(/\n|\r|\r\n/);
     80 
     81  // The sandbox for fails-if(), etc., condition evaluation. This is not
     82  // always required and so is created on demand.
     83  var sandbox;
     84  function GetOrCreateSandbox() {
     85    if (!sandbox) {
     86      sandbox = BuildConditionSandbox(aURL);
     87    }
     88    return sandbox;
     89  }
     90 
     91  var mozharness_test_paths = Services.prefs.getBoolPref(
     92    "reftest.mozharness_test_paths"
     93  );
     94  var lineNo = 0;
     95  var urlprefix = "";
     96  var defaults = [];
     97  var defaultTestPrefSettings = [],
     98    defaultRefPrefSettings = [];
     99  if (g.compareRetainedDisplayLists) {
    100    AddRetainedDisplayListTestPrefs(
    101      GetOrCreateSandbox(),
    102      defaultTestPrefSettings,
    103      defaultRefPrefSettings
    104    );
    105  }
    106  for (var str of lines) {
    107    ++lineNo;
    108    if (str.charAt(0) == "#") {
    109      continue;
    110    } // entire line was a comment
    111    var i = str.search(/\s+#/);
    112    if (i >= 0) {
    113      str = str.substring(0, i);
    114    }
    115    // strip leading and trailing whitespace
    116    str = str.replace(/^\s*/, "").replace(/\s*$/, "");
    117    if (!str || str == "") {
    118      continue;
    119    }
    120    var items = str.split(/\s+/); // split on whitespace
    121 
    122    if (items[0] == "url-prefix") {
    123      if (items.length != 2) {
    124        throw new Error(
    125          "url-prefix requires one url in manifest file " +
    126            aURL.spec +
    127            " line " +
    128            lineNo
    129        );
    130      }
    131      urlprefix = items[1];
    132      continue;
    133    }
    134 
    135    if (items[0] == "defaults") {
    136      items.shift();
    137      defaults = items;
    138      continue;
    139    }
    140 
    141    var expected_status = EXPECTED_PASS;
    142    var allow_silent_fail = false;
    143    var minAsserts = 0;
    144    var maxAsserts = 0;
    145    var needs_focus = false;
    146    var slow = false;
    147    var skip = false;
    148    var testPrefSettings = defaultTestPrefSettings.concat();
    149    var refPrefSettings = defaultRefPrefSettings.concat();
    150    var fuzzy_delta = { min: 0, max: 2 };
    151    var fuzzy_pixels = { min: 0, max: 1 };
    152    var chaosMode = false;
    153    var wrCapture = { test: false, ref: false };
    154    var nonSkipUsed = false;
    155    var noAutoFuzz = false;
    156 
    157    var origLength = items.length;
    158    items = defaults.concat(items);
    159    var modifiers = [...items];
    160    while (
    161      items[0].match(
    162        /^(fails|needs-focus|random|skip|asserts|slow|require-or|silentfail|pref|test-pref|ref-pref|fuzzy|chaos-mode|wr-capture|wr-capture-ref|noautofuzz)/
    163      )
    164    ) {
    165      var item = items.shift();
    166      var stat;
    167      var cond;
    168      var m = item.match(/^(fails|random|skip|silentfail)-if(\(.*\))$/);
    169      if (m) {
    170        stat = m[1];
    171        // Note: m[2] contains the parentheses, and we want them.
    172        cond = Cu.evalInSandbox(m[2], GetOrCreateSandbox());
    173      } else if (item.match(/^(fails|random|skip)$/)) {
    174        stat = item;
    175        cond = true;
    176      } else if (item == "needs-focus") {
    177        needs_focus = true;
    178        cond = false;
    179      } else if ((m = item.match(/^asserts\((\d+)(-\d+)?\)$/))) {
    180        cond = false;
    181        minAsserts = Number(m[1]);
    182        maxAsserts = m[2] == undefined ? minAsserts : Number(m[2].substring(1));
    183      } else if ((m = item.match(/^asserts-if\((.*?),(\d+)(-\d+)?\)$/))) {
    184        cond = false;
    185        if (Cu.evalInSandbox("(" + m[1] + ")", GetOrCreateSandbox())) {
    186          minAsserts = Number(m[2]);
    187          maxAsserts =
    188            m[3] == undefined ? minAsserts : Number(m[3].substring(1));
    189        }
    190      } else if (item == "slow") {
    191        cond = false;
    192        slow = true;
    193      } else if ((m = item.match(/^require-or\((.*?)\)$/))) {
    194        var args = m[1].split(/,/);
    195        if (args.length != 2) {
    196          throw new Error(
    197            "Error in manifest file " +
    198              aURL.spec +
    199              " line " +
    200              lineNo +
    201              ": wrong number of args to require-or"
    202          );
    203        }
    204        var [precondition_str, fallback_action] = args;
    205        var preconditions = precondition_str.split(/&&/);
    206        cond = false;
    207        for (var precondition of preconditions) {
    208          if (precondition === "debugMode") {
    209            // Currently unimplemented. Requires asynchronous
    210            // JSD call + getting an event while no JS is running
    211            stat = fallback_action;
    212            cond = true;
    213            break;
    214          } else if (precondition === "true") {
    215            // For testing
    216          } else {
    217            // Unknown precondition. Assume it is unimplemented.
    218            stat = fallback_action;
    219            cond = true;
    220            break;
    221          }
    222        }
    223      } else if ((m = item.match(/^slow-if\((.*?)\)$/))) {
    224        cond = false;
    225        if (Cu.evalInSandbox("(" + m[1] + ")", GetOrCreateSandbox())) {
    226          slow = true;
    227        }
    228      } else if (item == "silentfail") {
    229        cond = false;
    230        allow_silent_fail = true;
    231      } else if ((m = item.match(RE_PREF_ITEM))) {
    232        cond = false;
    233        if (
    234          !AddPrefSettings(
    235            m[1],
    236            m[2],
    237            m[3],
    238            GetOrCreateSandbox(),
    239            testPrefSettings,
    240            refPrefSettings
    241          )
    242        ) {
    243          throw new Error(
    244            "Error in pref value in manifest file " +
    245              aURL.spec +
    246              " line " +
    247              lineNo
    248          );
    249        }
    250      } else if ((m = item.match(/^fuzzy\((\d+)-(\d+),(\d+)-(\d+)\)$/))) {
    251        cond = false;
    252        expected_status = EXPECTED_FUZZY;
    253        fuzzy_delta = ExtractRange(m, 1);
    254        fuzzy_pixels = ExtractRange(m, 3);
    255      } else if (
    256        (m = item.match(/^fuzzy-if\((.*?),(\d+)-(\d+),(\d+)-(\d+)\)$/))
    257      ) {
    258        cond = false;
    259        if (Cu.evalInSandbox("(" + m[1] + ")", GetOrCreateSandbox())) {
    260          expected_status = EXPECTED_FUZZY;
    261          fuzzy_delta = ExtractRange(m, 2);
    262          fuzzy_pixels = ExtractRange(m, 4);
    263        }
    264      } else if (item == "chaos-mode") {
    265        cond = false;
    266        chaosMode = true;
    267      } else if (item == "wr-capture") {
    268        cond = false;
    269        wrCapture.test = true;
    270      } else if (item == "wr-capture-ref") {
    271        cond = false;
    272        wrCapture.ref = true;
    273      } else if (item == "noautofuzz") {
    274        cond = false;
    275        noAutoFuzz = true;
    276      } else {
    277        throw new Error(
    278          "Error in manifest file " +
    279            aURL.spec +
    280            " line " +
    281            lineNo +
    282            ": unexpected item " +
    283            item
    284        );
    285      }
    286 
    287      if (stat != "skip") {
    288        nonSkipUsed = true;
    289      }
    290 
    291      if (cond) {
    292        if (stat == "fails") {
    293          expected_status = EXPECTED_FAIL;
    294        } else if (stat == "random") {
    295          expected_status = EXPECTED_RANDOM;
    296        } else if (stat == "skip") {
    297          skip = true;
    298        } else if (stat == "silentfail") {
    299          allow_silent_fail = true;
    300        }
    301      }
    302    }
    303 
    304    if (items.length > origLength) {
    305      // Implies we broke out of the loop before we finished processing
    306      // defaults. This means defaults contained an invalid token.
    307      throw new Error(
    308        "Error in manifest file " +
    309          aURL.spec +
    310          " line " +
    311          lineNo +
    312          ": invalid defaults token '" +
    313          items[0] +
    314          "'"
    315      );
    316    }
    317 
    318    if (minAsserts > maxAsserts) {
    319      throw new Error(
    320        "Bad range in manifest file " + aURL.spec + " line " + lineNo
    321      );
    322    }
    323 
    324    var runHttp = false;
    325    var httpDepth;
    326    if (items[0] == "HTTP") {
    327      runHttp = aURL.scheme == "file"; // We can't yet run the local HTTP server
    328      // for non-local reftests.
    329      httpDepth = 0;
    330      items.shift();
    331    } else if (items[0].match(/HTTP\(\.\.(\/\.\.)*\)/)) {
    332      // Accept HTTP(..), HTTP(../..), HTTP(../../..), etc.
    333      runHttp = aURL.scheme == "file"; // We can't yet run the local HTTP server
    334      // for non-local reftests.
    335      httpDepth = (items[0].length - 5) / 3;
    336      items.shift();
    337    }
    338 
    339    // do not prefix the url for include commands or urls specifying
    340    // a protocol
    341    if (urlprefix && items[0] != "include") {
    342      if (items.length > 1 && !items[1].match(RE_PROTOCOL)) {
    343        items[1] = urlprefix + items[1];
    344      }
    345      if (items.length > 2 && !items[2].match(RE_PROTOCOL)) {
    346        items[2] = urlprefix + items[2];
    347      }
    348    }
    349 
    350    var principal = Services.scriptSecurityManager.createContentPrincipal(
    351      aURL,
    352      {}
    353    );
    354 
    355    if (items[0] == "include") {
    356      if (items.length != 2) {
    357        throw new Error(
    358          "Error in manifest file " +
    359            aURL.spec +
    360            " line " +
    361            lineNo +
    362            ": incorrect number of arguments to include"
    363        );
    364      }
    365      if (runHttp) {
    366        throw new Error(
    367          "Error in manifest file " +
    368            aURL.spec +
    369            " line " +
    370            lineNo +
    371            ": use of include with http"
    372        );
    373      }
    374 
    375      // If the expected_status is EXPECTED_PASS (the default) then allow
    376      // the include. If 'skip' is true, that means there was a skip
    377      // or skip-if annotation (with a true condition) on this include
    378      // statement, so we should skip the include. Any other expected_status
    379      // is disallowed since it's nonintuitive as to what the intended
    380      // effect is.
    381      if (nonSkipUsed) {
    382        throw new Error(
    383          "Error in manifest file " +
    384            aURL.spec +
    385            " line " +
    386            lineNo +
    387            ": include statement with annotation other than 'skip' or 'skip-if'"
    388        );
    389      } else if (skip) {
    390        g.logger.info(
    391          "Skipping included manifest at " +
    392            aURL.spec +
    393            " line " +
    394            lineNo +
    395            " due to matching skip condition"
    396        );
    397      } else {
    398        // poor man's assertion
    399        if (expected_status != EXPECTED_PASS) {
    400          throw new Error(
    401            "Error in manifest file parsing code: we should never get expected_status=" +
    402              expected_status +
    403              " when nonSkipUsed=false (from " +
    404              aURL.spec +
    405              " line " +
    406              lineNo +
    407              ")"
    408          );
    409        }
    410 
    411        var incURI = g.ioService.newURI(items[1], null, listURL);
    412        Services.scriptSecurityManager.checkLoadURIWithPrincipal(
    413          principal,
    414          incURI,
    415          Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT
    416        );
    417 
    418        // Cannot use nsIFile or similar to manipulate the manifest ID; although it appears
    419        // path-like, it does not refer to an actual path in the filesystem.
    420        var newManifestID = aManifestID;
    421        var included = items[1];
    422        // Remove included manifest file name.
    423        // eg. dir1/dir2/reftest.list -> dir1/dir2
    424        var pos = included.lastIndexOf("/");
    425        if (pos <= 0) {
    426          included = "";
    427        } else {
    428          included = included.substring(0, pos);
    429        }
    430        // Simplify references to parent directories.
    431        // eg. dir1/dir2/../dir3 -> dir1/dir3
    432        while (included.startsWith("../")) {
    433          pos = newManifestID.lastIndexOf("/");
    434          if (pos < 0) {
    435            pos = 0;
    436          }
    437          newManifestID = newManifestID.substring(0, pos);
    438          included = included.substring(3);
    439        }
    440        // Use a new manifest ID if the included manifest is in a different directory.
    441        if (included.length) {
    442          if (newManifestID.length) {
    443            newManifestID = newManifestID + "/" + included;
    444          } else {
    445            // parent directory includes may refer to the topsrcdir
    446            newManifestID = included;
    447          }
    448        }
    449        if (mozharness_test_paths) {
    450          g.logger.info(
    451            "Not recursively reading when MOZHARNESS_TEST_PATHS is set: " +
    452              items[1]
    453          );
    454        } else {
    455          ReadManifest(incURI, aFilter, newManifestID);
    456        }
    457      }
    458    } else if (items[0] == TYPE_LOAD || items[0] == TYPE_SCRIPT) {
    459      let type = items[0];
    460      if (items.length != 2) {
    461        throw new Error(
    462          "Error in manifest file " +
    463            aURL.spec +
    464            " line " +
    465            lineNo +
    466            ": incorrect number of arguments to " +
    467            type
    468        );
    469      }
    470      if (type == TYPE_LOAD && expected_status != EXPECTED_PASS) {
    471        throw new Error(
    472          "Error in manifest file " +
    473            aURL.spec +
    474            " line " +
    475            lineNo +
    476            ": incorrect known failure type for load test"
    477        );
    478      }
    479      AddTestItem(
    480        {
    481          type,
    482          expected: expected_status,
    483          manifest: aURL.spec,
    484          manifestID: TestIdentifier(aURL.spec, aManifestID),
    485          allowSilentFail: allow_silent_fail,
    486          minAsserts,
    487          maxAsserts,
    488          needsFocus: needs_focus,
    489          slow,
    490          skip,
    491          prefSettings1: testPrefSettings,
    492          prefSettings2: refPrefSettings,
    493          fuzzyMinDelta: fuzzy_delta.min,
    494          fuzzyMaxDelta: fuzzy_delta.max,
    495          fuzzyMinPixels: fuzzy_pixels.min,
    496          fuzzyMaxPixels: fuzzy_pixels.max,
    497          runHttp,
    498          httpDepth,
    499          url1: items[1],
    500          url2: null,
    501          chaosMode,
    502          wrCapture,
    503          noAutoFuzz,
    504          modifiers,
    505        },
    506        aFilter,
    507        aManifestID
    508      );
    509    } else if (
    510      items[0] == TYPE_REFTEST_EQUAL ||
    511      items[0] == TYPE_REFTEST_NOTEQUAL ||
    512      items[0] == TYPE_PRINT
    513    ) {
    514      if (items.length != 3) {
    515        throw new Error(
    516          "Error in manifest file " +
    517            aURL.spec +
    518            " line " +
    519            lineNo +
    520            ": incorrect number of arguments to " +
    521            items[0]
    522        );
    523      }
    524 
    525      if (
    526        items[0] == TYPE_REFTEST_NOTEQUAL &&
    527        expected_status == EXPECTED_FUZZY &&
    528        (fuzzy_delta.min > 0 || fuzzy_pixels.min > 0)
    529      ) {
    530        throw new Error(
    531          "Error in manifest file " +
    532            aURL.spec +
    533            " line " +
    534            lineNo +
    535            ": minimum fuzz must be zero for tests of type " +
    536            items[0]
    537        );
    538      }
    539 
    540      let type = items[0];
    541      if (g.compareRetainedDisplayLists) {
    542        type = TYPE_REFTEST_EQUAL;
    543 
    544        // We expect twice as many assertion failures when comparing
    545        // tests because we run each test twice.
    546        minAsserts *= 2;
    547        maxAsserts *= 2;
    548 
    549        // Skip the test if it is expected to fail in both modes.
    550        // It would unexpectedly "pass" in comparison mode mode when
    551        // comparing the two failures, which is not a useful result.
    552        if (
    553          expected_status === EXPECTED_FAIL ||
    554          expected_status === EXPECTED_RANDOM
    555        ) {
    556          skip = true;
    557        }
    558      }
    559 
    560      AddTestItem(
    561        {
    562          type,
    563          expected: expected_status,
    564          manifest: aURL.spec,
    565          manifestID: TestIdentifier(aURL.spec, aManifestID),
    566          allowSilentFail: allow_silent_fail,
    567          minAsserts,
    568          maxAsserts,
    569          needsFocus: needs_focus,
    570          slow,
    571          skip,
    572          prefSettings1: testPrefSettings,
    573          prefSettings2: refPrefSettings,
    574          fuzzyMinDelta: fuzzy_delta.min,
    575          fuzzyMaxDelta: fuzzy_delta.max,
    576          fuzzyMinPixels: fuzzy_pixels.min,
    577          fuzzyMaxPixels: fuzzy_pixels.max,
    578          runHttp,
    579          httpDepth,
    580          url1: items[1],
    581          url2: items[2],
    582          chaosMode,
    583          wrCapture,
    584          noAutoFuzz,
    585          modifiers,
    586        },
    587        aFilter,
    588        aManifestID
    589      );
    590    } else {
    591      throw new Error(
    592        "Error in manifest file " +
    593          aURL.spec +
    594          " line " +
    595          lineNo +
    596          ": unknown test type " +
    597          items[0]
    598      );
    599    }
    600  }
    601 }
    602 
    603 // Read all available data from an input stream and return it
    604 // as a string.
    605 function getStreamContent(inputStream) {
    606  var streamBuf = "";
    607  var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
    608    Ci.nsIScriptableInputStream
    609  );
    610  sis.init(inputStream);
    611 
    612  var available;
    613  while ((available = sis.available()) != 0) {
    614    streamBuf += sis.read(available);
    615  }
    616 
    617  return streamBuf;
    618 }
    619 
    620 // Build the sandbox for fails-if(), etc., condition evaluation.
    621 function BuildConditionSandbox(aURL) {
    622  var sandbox = new Cu.Sandbox(aURL.spec);
    623  sandbox.mozinfo = Services.prefs.getStringPref("sandbox.mozinfo", {});
    624  let mozinfo = JSON.parse(sandbox.mozinfo);
    625 
    626  // Shortcuts for widget toolkits.
    627  sandbox.Android = mozinfo.os == "android";
    628  sandbox.cocoaWidget = mozinfo.toolkit == "cocoa";
    629  sandbox.gtkWidget = mozinfo.toolkit == "gtk";
    630  sandbox.winWidget = mozinfo.toolkit == "windows";
    631 
    632  // arch
    633  sandbox.is64Bit = mozinfo.bits == "64"; // to be replaced by x86_64 or aarch64
    634  sandbox.x86 = mozinfo.processor == "x86";
    635  sandbox.x86_64 = mozinfo.processor == "x86_64";
    636  sandbox.aarch64 = mozinfo.processor == "aarch64";
    637 
    638  // build type
    639  sandbox.mingw = mozinfo.mingw;
    640  sandbox.isDebugBuild = mozinfo.debug;
    641  sandbox.isCoverageBuild = mozinfo.ccov;
    642  sandbox.AddressSanitizer = mozinfo.asan;
    643  sandbox.ThreadSanitizer = mozinfo.tsan;
    644  sandbox.optimized =
    645    !sandbox.isDebugBuild &&
    646    !sandbox.isCoverageBuild &&
    647    !sandbox.AddressSanitizer &&
    648    !sandbox.ThreadSanitizer;
    649 
    650  sandbox.release_or_beta = mozinfo.release_or_beta;
    651 
    652  // config specific prefs
    653  sandbox.appleSilicon = mozinfo.apple_silicon;
    654  sandbox.os_version = mozinfo.os_version;
    655  sandbox.wayland = mozinfo.display == "wayland";
    656 
    657  // data not using mozinfo
    658  sandbox.xulRuntime = Cu.cloneInto({}, sandbox);
    659 
    660  // Do we *not* have a dedicated gpu process.
    661  sandbox.nogpu =
    662    sandbox.wayland ||
    663    !(
    664      Services.prefs.getBoolPref("layers.gpu-process.enabled") &&
    665      Services.prefs.getBoolPref("layers.gpu-process.force-enabled")
    666    );
    667 
    668  var gfxInfo =
    669    NS_GFXINFO_CONTRACTID in Cc &&
    670    Cc[NS_GFXINFO_CONTRACTID].getService(Ci.nsIGfxInfo);
    671  let readGfxInfo = function (obj, key) {
    672    if (g.contentGfxInfo && key in g.contentGfxInfo) {
    673      return g.contentGfxInfo[key];
    674    }
    675    return obj[key];
    676  };
    677 
    678  sandbox.swgl = g.windowUtils.layerManagerType.startsWith(
    679    "WebRender (Software"
    680  );
    681  // These detect if each SVG filter primitive is enabled in WebRender
    682  sandbox.gfxSVGFE =
    683    Services.prefs.getBoolPref("gfx.webrender.svg-filter-effects") &&
    684    !g.useDrawSnapshot;
    685  sandbox.gfxSVGFEBlend =
    686    Services.prefs.getBoolPref("gfx.webrender.svg-filter-effects.feblend") &&
    687    sandbox.gfxSVGFE;
    688  sandbox.gfxSVGFEColorMatrix =
    689    Services.prefs.getBoolPref(
    690      "gfx.webrender.svg-filter-effects.fecolormatrix"
    691    ) && sandbox.gfxSVGFE;
    692  sandbox.gfxSVGFEComponentTransfer =
    693    Services.prefs.getBoolPref(
    694      "gfx.webrender.svg-filter-effects.fecomponenttransfer"
    695    ) && sandbox.gfxSVGFE;
    696  sandbox.gfxSVGFEComposite =
    697    Services.prefs.getBoolPref(
    698      "gfx.webrender.svg-filter-effects.fecomposite"
    699    ) && sandbox.gfxSVGFE;
    700  sandbox.gfxSVGFEDropShadow =
    701    Services.prefs.getBoolPref(
    702      "gfx.webrender.svg-filter-effects.fedropshadow"
    703    ) && sandbox.gfxSVGFE;
    704  sandbox.gfxSVGFEFlood =
    705    Services.prefs.getBoolPref("gfx.webrender.svg-filter-effects.feflood") &&
    706    sandbox.gfxSVGFE;
    707  sandbox.gfxSVGFEGaussianBlur =
    708    Services.prefs.getBoolPref(
    709      "gfx.webrender.svg-filter-effects.fegaussianblur"
    710    ) && sandbox.gfxSVGFE;
    711  sandbox.gfxSVGFEOffset =
    712    Services.prefs.getBoolPref("gfx.webrender.svg-filter-effects.feoffset") &&
    713    sandbox.gfxSVGFE;
    714 
    715  // Use this to annotate reftests that fail in drawSnapshot, but
    716  // the reason hasn't been investigated (or fixed) yet.
    717  sandbox.useDrawSnapshot = g.useDrawSnapshot;
    718 
    719  // GeckoView is currently uniquely identified by "android + e10s" but
    720  // we might want to make this condition more precise in the future.
    721  sandbox.geckoview = sandbox.Android && g.browserIsRemote;
    722  sandbox.isolated_process = sandbox.Android && mozinfo.isolated_process;
    723 
    724  if (sandbox.Android) {
    725    sandbox.AndroidVersion = Services.sysinfo.getPropertyAsInt32("version");
    726 
    727    sandbox.emulator = readGfxInfo(gfxInfo, "adapterDeviceID").includes(
    728      "Android Emulator"
    729    );
    730    sandbox.device = !sandbox.emulator;
    731  }
    732 
    733  // Some reftests need extra fuzz on the Android 13 Pixel 5 devices.
    734  sandbox.Android13 = sandbox.AndroidVersion == "33";
    735 
    736  // always true except for windows mingwclang builds
    737  sandbox.webrtc = AppConstants.MOZ_WEBRTC;
    738 
    739  sandbox.prefs = Cu.cloneInto(
    740    {
    741      getBoolPref(p) {
    742        return Services.prefs.getBoolPref(p);
    743      },
    744      getIntPref(p) {
    745        return Services.prefs.getIntPref(p);
    746      },
    747    },
    748    sandbox,
    749    { cloneFunctions: true }
    750  );
    751 
    752  // Running with a variant enabled?
    753  sandbox.fission = Services.appinfo.fissionAutostart;
    754 
    755  sandbox.incOriginInit =
    756    Services.env.get("MOZ_ENABLE_INC_ORIGIN_INIT") === "1";
    757 
    758  if (!g.dumpedConditionSandbox) {
    759    g.logger.info(
    760      "Dumping representation of sandbox which can be used for expectation annotations"
    761    );
    762    for (let entry of Object.entries(Cu.waiveXrays(sandbox)).sort((a, b) =>
    763      a[0].localeCompare(b[0])
    764    )) {
    765      let value =
    766        typeof entry[1] === "object" ? JSON.stringify(entry[1]) : entry[1];
    767      g.logger.info(`    ${entry[0]}: ${value}`);
    768    }
    769    g.dumpedConditionSandbox = true;
    770  }
    771 
    772  return sandbox;
    773 }
    774 
    775 function AddRetainedDisplayListTestPrefs(
    776  aSandbox,
    777  aTestPrefSettings,
    778  aRefPrefSettings
    779 ) {
    780  AddPrefSettings(
    781    "test-",
    782    "layout.display-list.retain",
    783    "true",
    784    aSandbox,
    785    aTestPrefSettings,
    786    aRefPrefSettings
    787  );
    788  AddPrefSettings(
    789    "ref-",
    790    "layout.display-list.retain",
    791    "false",
    792    aSandbox,
    793    aTestPrefSettings,
    794    aRefPrefSettings
    795  );
    796 }
    797 
    798 function AddPrefSettings(
    799  aWhere,
    800  aPrefName,
    801  aPrefValExpression,
    802  aSandbox,
    803  aTestPrefSettings,
    804  aRefPrefSettings
    805 ) {
    806  var prefVal = Cu.evalInSandbox("(" + aPrefValExpression + ")", aSandbox);
    807  var prefType;
    808  var valType = typeof prefVal;
    809  if (valType == "boolean") {
    810    prefType = PREF_BOOLEAN;
    811  } else if (valType == "string") {
    812    prefType = PREF_STRING;
    813  } else if (valType == "number" && parseInt(prefVal) == prefVal) {
    814    prefType = PREF_INTEGER;
    815  } else {
    816    return false;
    817  }
    818  var setting = { name: aPrefName, type: prefType, value: prefVal };
    819 
    820  if (
    821    g.compareRetainedDisplayLists &&
    822    aPrefName != "layout.display-list.retain"
    823  ) {
    824    // ref-pref() is ignored, test-pref() and pref() are added to both
    825    if (aWhere != "ref-") {
    826      aTestPrefSettings.push(setting);
    827      aRefPrefSettings.push(setting);
    828    }
    829  } else {
    830    if (aWhere != "ref-") {
    831      aTestPrefSettings.push(setting);
    832    }
    833    if (aWhere != "test-") {
    834      aRefPrefSettings.push(setting);
    835    }
    836  }
    837  return true;
    838 }
    839 
    840 function ExtractRange(matches, startIndex) {
    841  return {
    842    min: Number(matches[startIndex]),
    843    max: Number(matches[startIndex + 1]),
    844  };
    845 }
    846 
    847 function ServeTestBase(aURL, depth) {
    848  var listURL = aURL.QueryInterface(Ci.nsIFileURL);
    849  var directory = listURL.file.parent;
    850 
    851  // Allow serving a tree that's an ancestor of the directory containing
    852  // the files so that they can use resources in ../ (etc.).
    853  var dirPath = "/";
    854  while (depth > 0) {
    855    dirPath = "/" + directory.leafName + dirPath;
    856    directory = directory.parent;
    857    --depth;
    858  }
    859 
    860  g.count++;
    861  var path = "/" + Date.now() + "/" + g.count;
    862  g.server.registerDirectory(path + "/", directory);
    863  // this one is needed so tests can use example.org urls for cross origin testing
    864  g.server.registerDirectory("/", directory);
    865 
    866  return g.ioService.newURI(
    867    "http://localhost:" + g.httpServerPort + path + dirPath
    868  );
    869 }
    870 
    871 export function CreateUrls(test) {
    872  let manifestURL = g.ioService.newURI(test.manifest);
    873 
    874  let testbase = manifestURL;
    875  if (test.runHttp) {
    876    testbase = ServeTestBase(manifestURL, test.httpDepth);
    877  }
    878 
    879  let testbasePrincipal = Services.scriptSecurityManager.createContentPrincipal(
    880    testbase,
    881    {}
    882  );
    883  Services.perms.addFromPrincipal(
    884    testbasePrincipal,
    885    "allowXULXBL",
    886    Services.perms.ALLOW_ACTION
    887  );
    888 
    889  function FileToURI(file) {
    890    if (file === null) {
    891      return file;
    892    }
    893 
    894    var testURI = g.ioService.newURI(file, null, testbase);
    895    let isChromeOrViewSource =
    896      testURI.scheme == "chrome" || testURI.scheme == "view-source";
    897    let principal = isChromeOrViewSource
    898      ? Services.scriptSecurityManager.getSystemPrincipal()
    899      : Services.scriptSecurityManager.createContentPrincipal(manifestURL, {});
    900    Services.scriptSecurityManager.checkLoadURIWithPrincipal(
    901      principal,
    902      testURI,
    903      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT
    904    );
    905    return testURI;
    906  }
    907 
    908  let files = [test.url1, test.url2];
    909  [test.url1, test.url2] = files.map(FileToURI);
    910 
    911  return test;
    912 }
    913 
    914 function TestIdentifier(aUrl, aManifestID) {
    915  // Construct a platform-independent and location-independent test identifier for
    916  // a url; normally the identifier looks like a posix-compliant relative file
    917  // path.
    918  // Test urls may be simple file names, chrome: urls with full paths, about:blank, etc.
    919  if (aUrl.startsWith("about:") || aUrl.startsWith("data:")) {
    920    return aUrl;
    921  }
    922  var pos = aUrl.lastIndexOf("/");
    923  var url = pos < 0 ? aUrl : aUrl.substring(pos + 1);
    924  return aManifestID + "/" + url;
    925 }
    926 
    927 function AddTestItem(aTest, aFilter, aManifestID) {
    928  if (!aFilter) {
    929    aFilter = [null, [], false];
    930  }
    931 
    932  var identifier = TestIdentifier(aTest.url1, aManifestID);
    933  if (aTest.url2 !== null) {
    934    identifier = [
    935      identifier,
    936      aTest.type,
    937      TestIdentifier(aTest.url2, aManifestID),
    938    ];
    939  }
    940 
    941  var { url1, url2 } = CreateUrls(Object.assign({}, aTest));
    942 
    943  var globalFilter = aFilter[0];
    944  var manifestFilter = aFilter[1];
    945  var invertManifest = aFilter[2];
    946  if (globalFilter && !globalFilter.test(url1.spec)) {
    947    if (url2 === null) {
    948      return;
    949    }
    950    if (globalFilter && !globalFilter.test(url2.spec)) {
    951      return;
    952    }
    953  }
    954  if (manifestFilter && !(invertManifest ^ manifestFilter.test(url1.spec))) {
    955    if (url2 === null) {
    956      return;
    957    }
    958    if (manifestFilter && !(invertManifest ^ manifestFilter.test(url2.spec))) {
    959      return;
    960    }
    961  }
    962  if (
    963    g.focusFilterMode == FOCUS_FILTER_NEEDS_FOCUS_TESTS &&
    964    !aTest.needsFocus
    965  ) {
    966    return;
    967  }
    968  if (
    969    g.focusFilterMode == FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS &&
    970    aTest.needsFocus
    971  ) {
    972    return;
    973  }
    974 
    975  aTest.identifier = identifier;
    976  g.urls.push(aTest);
    977  // Periodically log progress to avoid no-output timeout on slow platforms.
    978  // No-output timeouts during manifest parsing have been a problem for
    979  // jsreftests on Android/debug. Any logging resets the no-output timer,
    980  // even debug logging which is normally not displayed.
    981  if (g.urls.length % 5000 == 0) {
    982    g.logger.debug(g.urls.length + " tests found...");
    983  }
    984 }