1/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- /
2/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
3/* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6"use strict";
7
8var EXPORTED_SYMBOLS = ["ReadTopManifest", "CreateUrls"];
9
10Cu.import("resource://reftest/globals.jsm", this);
11Cu.import("resource://reftest/reftest.jsm", this);
12Cu.import("resource://gre/modules/Services.jsm");
13Cu.import("resource://gre/modules/NetUtil.jsm");
14
15const NS_SCRIPTSECURITYMANAGER_CONTRACTID = "@mozilla.org/scriptsecuritymanager;1";
16const NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX = "@mozilla.org/network/protocol;1?name=";
17const NS_XREAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
18
19const RE_PROTOCOL = /^\w+:/;
20const RE_PREF_ITEM = /^(|test-|ref-)pref\((.+?),(.*)\)$/;
21
22
23function ReadTopManifest(aFileURL, aFilter, aManifestID)
24{
25    var url = g.ioService.newURI(aFileURL);
26    if (!url)
27        throw "Expected a file or http URL for the manifest.";
28
29    g.manifestsLoaded = {};
30    ReadManifest(url, aFilter, aManifestID);
31}
32
33// Note: If you materially change the reftest manifest parsing,
34// please keep the parser in layout/tools/reftest/__init__.py in sync.
35function ReadManifest(aURL, aFilter, aManifestID)
36{
37    // Ensure each manifest is only read once. This assumes that manifests that
38    // are included with filters will be read via their include before they are
39    // read directly in the case of a duplicate
40    if (g.manifestsLoaded.hasOwnProperty(aURL.spec)) {
41        if (g.manifestsLoaded[aURL.spec] === null)
42            return;
43        else
44            aFilter = [aFilter[0], aFilter[1], true];
45    }
46    g.manifestsLoaded[aURL.spec] = aFilter[1];
47
48    var secMan = Cc[NS_SCRIPTSECURITYMANAGER_CONTRACTID]
49                     .getService(Ci.nsIScriptSecurityManager);
50
51    var listURL = aURL;
52    var channel = NetUtil.newChannel({uri: aURL,
53                                      loadUsingSystemPrincipal: true});
54    try {
55        var inputStream = channel.open();
56    } catch (e) {
57        g.logger.error("failed to open manifest at : " + aURL.spec);
58        throw e;
59    }
60    if (channel instanceof Ci.nsIHttpChannel
61        && channel.responseStatus != 200) {
62      g.logger.error("HTTP ERROR : " + channel.responseStatus);
63    }
64    var streamBuf = getStreamContent(inputStream);
65    inputStream.close();
66    var lines = streamBuf.split(/\n|\r|\r\n/);
67
68    // The sandbox for fails-if(), etc., condition evaluation. This is not
69    // always required and so is created on demand.
70    var sandbox;
71    function GetOrCreateSandbox() {
72        if (!sandbox) {
73            sandbox = BuildConditionSandbox(aURL);
74        }
75        return sandbox;
76    }
77
78    var lineNo = 0;
79    var urlprefix = "";
80    var defaults = [];
81    var defaultTestPrefSettings = [], defaultRefPrefSettings = [];
82    if (g.compareRetainedDisplayLists) {
83        AddRetainedDisplayListTestPrefs(GetOrCreateSandbox(), defaultTestPrefSettings,
84                                        defaultRefPrefSettings);
85    }
86    for (var str of lines) {
87        ++lineNo;
88        if (str.charAt(0) == "#")
89            continue; // entire line was a comment
90        var i = str.search(/\s+#/);
91        if (i >= 0)
92            str = str.substring(0, i);
93        // strip leading and trailing whitespace
94        str = str.replace(/^\s*/, '').replace(/\s*$/, '');
95        if (!str || str == "")
96            continue;
97        var items = str.split(/\s+/); // split on whitespace
98
99        if (items[0] == "url-prefix") {
100            if (items.length != 2)
101                throw "url-prefix requires one url in manifest file " + aURL.spec + " line " + lineNo;
102            urlprefix = items[1];
103            continue;
104        }
105
106        if (items[0] == "defaults") {
107            items.shift();
108            defaults = items;
109            continue;
110        }
111
112        var expected_status = EXPECTED_PASS;
113        var allow_silent_fail = false;
114        var minAsserts = 0;
115        var maxAsserts = 0;
116        var needs_focus = false;
117        var slow = false;
118        var skip = false;
119        var testPrefSettings = defaultTestPrefSettings.concat();
120        var refPrefSettings = defaultRefPrefSettings.concat();
121        var fuzzy_delta = { min: 0, max: 2 };
122        var fuzzy_pixels = { min: 0, max: 1 };
123        var chaosMode = false;
124        var wrCapture = { test: false, ref: false };
125        var nonSkipUsed = false;
126        var noAutoFuzz = false;
127
128        var origLength = items.length;
129        items = defaults.concat(items);
130        while (items[0].match(/^(fails|needs-focus|random|skip|asserts|slow|require-or|silentfail|pref|test-pref|ref-pref|fuzzy|chaos-mode|wr-capture|wr-capture-ref|noautofuzz)/)) {
131            var item = items.shift();
132            var stat;
133            var cond;
134            var m = item.match(/^(fails|random|skip|silentfail)-if(\(.*\))$/);
135            if (m) {
136                stat = m[1];
137                // Note: m[2] contains the parentheses, and we want them.
138                cond = Cu.evalInSandbox(m[2], GetOrCreateSandbox());
139            } else if (item.match(/^(fails|random|skip)$/)) {
140                stat = item;
141                cond = true;
142            } else if (item == "needs-focus") {
143                needs_focus = true;
144                cond = false;
145            } else if ((m = item.match(/^asserts\((\d+)(-\d+)?\)$/))) {
146                cond = false;
147                minAsserts = Number(m[1]);
148                maxAsserts = (m[2] == undefined) ? minAsserts
149                                                 : Number(m[2].substring(1));
150            } else if ((m = item.match(/^asserts-if\((.*?),(\d+)(-\d+)?\)$/))) {
151                cond = false;
152                if (Cu.evalInSandbox("(" + m[1] + ")", GetOrCreateSandbox())) {
153                    minAsserts = Number(m[2]);
154                    maxAsserts =
155                      (m[3] == undefined) ? minAsserts
156                                          : Number(m[3].substring(1));
157                }
158            } else if (item == "slow") {
159                cond = false;
160                slow = true;
161            } else if ((m = item.match(/^require-or\((.*?)\)$/))) {
162                var args = m[1].split(/,/);
163                if (args.length != 2) {
164                    throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": wrong number of args to require-or";
165                }
166                var [precondition_str, fallback_action] = args;
167                var preconditions = precondition_str.split(/&&/);
168                cond = false;
169                for (var precondition of preconditions) {
170                    if (precondition === "debugMode") {
171                        // Currently unimplemented. Requires asynchronous
172                        // JSD call + getting an event while no JS is running
173                        stat = fallback_action;
174                        cond = true;
175                        break;
176                    } else if (precondition === "true") {
177                        // For testing
178                    } else {
179                        // Unknown precondition. Assume it is unimplemented.
180                        stat = fallback_action;
181                        cond = true;
182                        break;
183                    }
184                }
185            } else if ((m = item.match(/^slow-if\((.*?)\)$/))) {
186                cond = false;
187                if (Cu.evalInSandbox("(" + m[1] + ")", GetOrCreateSandbox()))
188                    slow = true;
189            } else if (item == "silentfail") {
190                cond = false;
191                allow_silent_fail = true;
192            } else if ((m = item.match(RE_PREF_ITEM))) {
193                cond = false;
194                if (!AddPrefSettings(m[1], m[2], m[3], GetOrCreateSandbox(),
195                                     testPrefSettings, refPrefSettings)) {
196                    throw "Error in pref value in manifest file " + aURL.spec + " line " + lineNo;
197                }
198            } else if ((m = item.match(/^fuzzy\((\d+)-(\d+),(\d+)-(\d+)\)$/))) {
199              cond = false;
200              expected_status = EXPECTED_FUZZY;
201              fuzzy_delta = ExtractRange(m, 1);
202              fuzzy_pixels = ExtractRange(m, 3);
203            } else if ((m = item.match(/^fuzzy-if\((.*?),(\d+)-(\d+),(\d+)-(\d+)\)$/))) {
204              cond = false;
205              if (Cu.evalInSandbox("(" + m[1] + ")", GetOrCreateSandbox())) {
206                expected_status = EXPECTED_FUZZY;
207                fuzzy_delta = ExtractRange(m, 2);
208                fuzzy_pixels = ExtractRange(m, 4);
209              }
210            } else if (item == "chaos-mode") {
211                cond = false;
212                chaosMode = true;
213            } else if (item == "wr-capture") {
214                cond = false;
215                wrCapture.test = true;
216            } else if (item == "wr-capture-ref") {
217                cond = false;
218                wrCapture.ref = true;
219            } else if (item == "noautofuzz") {
220                cond = false;
221                noAutoFuzz = true;
222            } else {
223                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": unexpected item " + item;
224            }
225
226            if (stat != "skip") {
227                nonSkipUsed = true;
228            }
229
230            if (cond) {
231                if (stat == "fails") {
232                    expected_status = EXPECTED_FAIL;
233                } else if (stat == "random") {
234                    expected_status = EXPECTED_RANDOM;
235                } else if (stat == "skip") {
236                    skip = true;
237                } else if (stat == "silentfail") {
238                    allow_silent_fail = true;
239                }
240            }
241        }
242
243        if (items.length > origLength) {
244            // Implies we broke out of the loop before we finished processing
245            // defaults. This means defaults contained an invalid token.
246            throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": invalid defaults token '" + items[0] + "'";
247        }
248
249        if (minAsserts > maxAsserts) {
250            throw "Bad range in manifest file " + aURL.spec + " line " + lineNo;
251        }
252
253        var runHttp = false;
254        var httpDepth;
255        if (items[0] == "HTTP") {
256            runHttp = (aURL.scheme == "file"); // We can't yet run the local HTTP server
257                                               // for non-local reftests.
258            httpDepth = 0;
259            items.shift();
260        } else if (items[0].match(/HTTP\(\.\.(\/\.\.)*\)/)) {
261            // Accept HTTP(..), HTTP(../..), HTTP(../../..), etc.
262            runHttp = (aURL.scheme == "file"); // We can't yet run the local HTTP server
263                                               // for non-local reftests.
264            httpDepth = (items[0].length - 5) / 3;
265            items.shift();
266        }
267
268        // do not prefix the url for include commands or urls specifying
269        // a protocol
270        if (urlprefix && items[0] != "include") {
271            if (items.length > 1 && !items[1].match(RE_PROTOCOL)) {
272                items[1] = urlprefix + items[1];
273            }
274            if (items.length > 2 && !items[2].match(RE_PROTOCOL)) {
275                items[2] = urlprefix + items[2];
276            }
277        }
278
279        var principal = secMan.createContentPrincipal(aURL, {});
280
281        if (items[0] == "include") {
282            if (items.length != 2)
283                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to include";
284            if (runHttp)
285                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": use of include with http";
286
287            // If the expected_status is EXPECTED_PASS (the default) then allow
288            // the include. If 'skip' is true, that means there was a skip
289            // or skip-if annotation (with a true condition) on this include
290            // statement, so we should skip the include. Any other expected_status
291            // is disallowed since it's nonintuitive as to what the intended
292            // effect is.
293            if (nonSkipUsed) {
294                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": include statement with annotation other than 'skip' or 'skip-if'";
295            } else if (skip) {
296                g.logger.info("Skipping included manifest at " + aURL.spec + " line " + lineNo + " due to matching skip condition");
297            } else {
298                // poor man's assertion
299                if (expected_status != EXPECTED_PASS) {
300                    throw "Error in manifest file parsing code: we should never get expected_status=" + expected_status + " when nonSkipUsed=false (from " + aURL.spec + " line " + lineNo + ")";
301                }
302
303                var incURI = g.ioService.newURI(items[1], null, listURL);
304                secMan.checkLoadURIWithPrincipal(principal, incURI,
305                                                 Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
306
307                // Cannot use nsIFile or similar to manipulate the manifest ID; although it appears
308                // path-like, it does not refer to an actual path in the filesystem.
309                var newManifestID = aManifestID;
310                var included = items[1];
311                // Remove included manifest file name.
312                // eg. dir1/dir2/reftest.list -> dir1/dir2
313                var pos = included.lastIndexOf("/");
314                if (pos <= 0) {
315                    included = "";
316                } else {
317                    included = included.substring(0, pos);
318                }
319                // Simplify references to parent directories.
320                // eg. dir1/dir2/../dir3 -> dir1/dir3
321                while (included.startsWith("../")) {
322                    pos = newManifestID.lastIndexOf("/");
323                    if (pos < 0) {
324                        pos = 0;
325                    }
326                    newManifestID = newManifestID.substring(0, pos);
327                    included = included.substring(3);
328                }
329                // Use a new manifest ID if the included manifest is in a different directory.
330                if (included.length > 0) {
331                    if (newManifestID.length > 0) {
332                        newManifestID = newManifestID + "/" + included;
333                    } else {
334                        // parent directory includes may refer to the topsrcdir
335                        newManifestID = included;
336                    }
337                }
338                ReadManifest(incURI, aFilter, newManifestID);
339            }
340        } else if (items[0] == TYPE_LOAD || items[0] == TYPE_SCRIPT) {
341            var type = items[0];
342            if (items.length != 2)
343                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to " + type;
344            if (type == TYPE_LOAD && expected_status != EXPECTED_PASS)
345                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect known failure type for load test";
346            AddTestItem({ type: type,
347                          expected: expected_status,
348                          manifest: aURL.spec,
349                          manifestID: TestIdentifier(aURL.spec, aManifestID),
350                          allowSilentFail: allow_silent_fail,
351                          minAsserts: minAsserts,
352                          maxAsserts: maxAsserts,
353                          needsFocus: needs_focus,
354                          slow: slow,
355                          skip: skip,
356                          prefSettings1: testPrefSettings,
357                          prefSettings2: refPrefSettings,
358                          fuzzyMinDelta: fuzzy_delta.min,
359                          fuzzyMaxDelta: fuzzy_delta.max,
360                          fuzzyMinPixels: fuzzy_pixels.min,
361                          fuzzyMaxPixels: fuzzy_pixels.max,
362                          runHttp: runHttp,
363                          httpDepth: httpDepth,
364                          url1: items[1],
365                          url2: null,
366                          chaosMode: chaosMode,
367                          wrCapture: wrCapture,
368                          noAutoFuzz: noAutoFuzz }, aFilter, aManifestID);
369        } else if (items[0] == TYPE_REFTEST_EQUAL || items[0] == TYPE_REFTEST_NOTEQUAL || items[0] == TYPE_PRINT) {
370            if (items.length != 3)
371                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to " + items[0];
372
373            if (items[0] == TYPE_REFTEST_NOTEQUAL &&
374                expected_status == EXPECTED_FUZZY &&
375                (fuzzy_delta.min > 0 || fuzzy_pixels.min > 0)) {
376                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": minimum fuzz must be zero for tests of type " + items[0];
377            }
378
379            var type = items[0];
380            if (g.compareRetainedDisplayLists) {
381                type = TYPE_REFTEST_EQUAL;
382
383                // We expect twice as many assertion failures when comparing
384                // tests because we run each test twice.
385                minAsserts *= 2;
386                maxAsserts *= 2;
387
388                // Skip the test if it is expected to fail in both modes.
389                // It would unexpectedly "pass" in comparison mode mode when
390                // comparing the two failures, which is not a useful result.
391                if (expected_status === EXPECTED_FAIL ||
392                    expected_status === EXPECTED_RANDOM) {
393                    skip = true;
394                }
395            }
396
397            AddTestItem({ type: type,
398                          expected: expected_status,
399                          manifest: aURL.spec,
400                          manifestID: TestIdentifier(aURL.spec, aManifestID),
401                          allowSilentFail: allow_silent_fail,
402                          minAsserts: minAsserts,
403                          maxAsserts: maxAsserts,
404                          needsFocus: needs_focus,
405                          slow: slow,
406                          skip: skip,
407                          prefSettings1: testPrefSettings,
408                          prefSettings2: refPrefSettings,
409                          fuzzyMinDelta: fuzzy_delta.min,
410                          fuzzyMaxDelta: fuzzy_delta.max,
411                          fuzzyMinPixels: fuzzy_pixels.min,
412                          fuzzyMaxPixels: fuzzy_pixels.max,
413                          runHttp: runHttp,
414                          httpDepth: httpDepth,
415                          url1: items[1],
416                          url2: items[2],
417                          chaosMode: chaosMode,
418                          wrCapture: wrCapture,
419                          noAutoFuzz: noAutoFuzz }, aFilter, aManifestID);
420        } else {
421            throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": unknown test type " + items[0];
422        }
423    }
424}
425
426// Read all available data from an input stream and return it
427// as a string.
428function getStreamContent(inputStream)
429{
430    var streamBuf = "";
431    var sis = Cc["@mozilla.org/scriptableinputstream;1"].
432                  createInstance(Ci.nsIScriptableInputStream);
433    sis.init(inputStream);
434
435    var available;
436    while ((available = sis.available()) != 0) {
437        streamBuf += sis.read(available);
438    }
439
440    return streamBuf;
441}
442
443// Build the sandbox for fails-if(), etc., condition evaluation.
444function BuildConditionSandbox(aURL) {
445    var sandbox = new Cu.Sandbox(aURL.spec);
446    var xr = Cc[NS_XREAPPINFO_CONTRACTID].getService(Ci.nsIXULRuntime);
447    var appInfo = Cc[NS_XREAPPINFO_CONTRACTID].getService(Ci.nsIXULAppInfo);
448    sandbox.isDebugBuild = g.debug.isDebugBuild;
449    sandbox.isCoverageBuild = g.isCoverageBuild;
450    var prefs = Cc["@mozilla.org/preferences-service;1"].
451                getService(Ci.nsIPrefBranch);
452    var env = Cc["@mozilla.org/process/environment;1"].
453                getService(Ci.nsIEnvironment);
454
455    sandbox.xulRuntime = Cu.cloneInto({widgetToolkit: xr.widgetToolkit, OS: xr.OS, XPCOMABI: xr.XPCOMABI}, sandbox);
456
457    var testRect = g.browser.getBoundingClientRect();
458    sandbox.smallScreen = false;
459    if (g.containingWindow.innerWidth < 800 || g.containingWindow.innerHeight < 1000) {
460        sandbox.smallScreen = true;
461    }
462
463    var gfxInfo = (NS_GFXINFO_CONTRACTID in Cc) && Cc[NS_GFXINFO_CONTRACTID].getService(Ci.nsIGfxInfo);
464    let readGfxInfo = function (obj, key) {
465      if (g.contentGfxInfo && (key in g.contentGfxInfo)) {
466        return g.contentGfxInfo[key];
467      }
468      return obj[key];
469    }
470
471    try {
472      sandbox.d2d = readGfxInfo(gfxInfo, "D2DEnabled");
473      sandbox.dwrite = readGfxInfo(gfxInfo, "DWriteEnabled");
474      sandbox.embeddedInFirefoxReality = readGfxInfo(gfxInfo, "EmbeddedInFirefoxReality");
475    } catch (e) {
476      sandbox.d2d = false;
477      sandbox.dwrite = false;
478      sandbox.embeddedInFirefoxReality = false;
479    }
480
481    var canvasBackend = readGfxInfo(gfxInfo, "AzureCanvasBackend");
482    var contentBackend = readGfxInfo(gfxInfo, "AzureContentBackend");
483
484    sandbox.gpuProcess = gfxInfo.usingGPUProcess;
485    sandbox.azureCairo = canvasBackend == "cairo";
486    sandbox.azureSkia = canvasBackend == "skia";
487    sandbox.skiaContent = contentBackend == "skia";
488    sandbox.azureSkiaGL = false;
489    // true if we are using the same Azure backend for rendering canvas and content
490    sandbox.contentSameGfxBackendAsCanvas = contentBackend == canvasBackend
491                                            || (contentBackend == "none" && canvasBackend == "cairo");
492
493    sandbox.remoteCanvas = prefs.getBoolPref("gfx.canvas.remote") && sandbox.d2d && sandbox.gpuProcess;
494
495    sandbox.layersGPUAccelerated =
496      g.windowUtils.layerManagerType != "Basic";
497    sandbox.d3d11 =
498      g.windowUtils.layerManagerType == "Direct3D 11";
499    sandbox.d3d9 =
500      g.windowUtils.layerManagerType == "Direct3D 9";
501    sandbox.layersOpenGL =
502      g.windowUtils.layerManagerType == "OpenGL";
503    sandbox.swgl =
504      g.windowUtils.layerManagerType.startsWith("WebRender (Software");
505    sandbox.webrender =
506      g.windowUtils.layerManagerType.startsWith("WebRender");
507    sandbox.layersOMTC =
508      g.windowUtils.layerManagerRemote == true;
509    sandbox.advancedLayers =
510      g.windowUtils.usingAdvancedLayers == true;
511    sandbox.layerChecksEnabled = !sandbox.webrender;
512
513    sandbox.usesOverlayScrollbars = g.windowUtils.usesOverlayScrollbars;
514
515    // Shortcuts for widget toolkits.
516    sandbox.Android = xr.OS == "Android";
517    sandbox.cocoaWidget = xr.widgetToolkit == "cocoa";
518    sandbox.gtkWidget = xr.widgetToolkit == "gtk";
519    sandbox.qtWidget = xr.widgetToolkit == "qt";
520    sandbox.winWidget = xr.widgetToolkit == "windows";
521
522    sandbox.is64Bit = xr.is64Bit;
523
524    // Use this to annotate reftests that fail in drawSnapshot, but
525    // the reason hasn't been investigated (or fixed) yet.
526    sandbox.useDrawSnapshot = g.useDrawSnapshot;
527    // Use this to annotate reftests that use functionality
528    // that isn't available to drawSnapshot (like any sort of
529    // compositor feature such as async scrolling).
530    sandbox.unsupportedWithDrawSnapshot = g.useDrawSnapshot;
531
532    sandbox.retainedDisplayList =
533      prefs.getBoolPref("layout.display-list.retain") && !sandbox.useDrawSnapshot;
534
535    // GeckoView is currently uniquely identified by "android + e10s" but
536    // we might want to make this condition more precise in the future.
537    sandbox.geckoview = (sandbox.Android && g.browserIsRemote);
538
539    // Scrollbars that are semi-transparent. See bug 1169666.
540    sandbox.transparentScrollbars = xr.widgetToolkit == "gtk";
541
542    var sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
543    if (sandbox.Android) {
544        // This is currently used to distinguish Android 4.0.3 (SDK version 15)
545        // and later from Android 2.x
546        sandbox.AndroidVersion = sysInfo.getPropertyAsInt32("version");
547
548        sandbox.emulator = readGfxInfo(gfxInfo, "adapterDeviceID").includes("Android Emulator");
549        sandbox.device = !sandbox.emulator;
550    }
551
552    sandbox.MinGW = sandbox.winWidget && sysInfo.getPropertyAsBool("isMinGW");
553
554#if MOZ_ASAN
555    sandbox.AddressSanitizer = true;
556#else
557    sandbox.AddressSanitizer = false;
558#endif
559
560#if MOZ_TSAN
561    sandbox.ThreadSanitizer = true;
562#else
563    sandbox.ThreadSanitizer = false;
564#endif
565
566#if MOZ_WEBRTC
567    sandbox.webrtc = true;
568#else
569    sandbox.webrtc = false;
570#endif
571
572#if MOZ_JXL
573    sandbox.jxl = true;
574#else
575    sandbox.jxl = false;
576#endif
577
578    let retainedDisplayListsEnabled = prefs.getBoolPref("layout.display-list.retain", false);
579    sandbox.retainedDisplayLists = retainedDisplayListsEnabled && !g.compareRetainedDisplayLists && !sandbox.useDrawSnapshot;
580    sandbox.compareRetainedDisplayLists = g.compareRetainedDisplayLists;
581
582#ifdef RELEASE_OR_BETA
583    sandbox.release_or_beta = true;
584#else
585    sandbox.release_or_beta = false;
586#endif
587
588    var hh = Cc[NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX + "http"].
589                 getService(Ci.nsIHttpProtocolHandler);
590    var httpProps = ["userAgent", "appName", "appVersion", "vendor",
591                     "vendorSub", "product", "productSub", "platform",
592                     "oscpu", "language", "misc"];
593    sandbox.http = new sandbox.Object();
594    httpProps.forEach((x) => sandbox.http[x] = hh[x]);
595
596    // Set OSX to be the Mac OS X version, as an integer, or undefined
597    // for other platforms.  The integer is formed by 100 times the
598    // major version plus the minor version, so 1006 for 10.6, 1010 for
599    // 10.10, etc.
600    var osxmatch = /Mac OS X (\d+).(\d+)$/.exec(hh.oscpu);
601    sandbox.OSX = osxmatch ? parseInt(osxmatch[1]) * 100 + parseInt(osxmatch[2]) : undefined;
602
603    // config specific prefs
604    sandbox.appleSilicon = prefs.getBoolPref("sandbox.apple_silicon", false);
605
606    // Set a flag on sandbox if the windows default theme is active
607    sandbox.windowsDefaultTheme = g.containingWindow.matchMedia("(-moz-windows-default-theme)").matches;
608
609    try {
610        sandbox.nativeThemePref = !prefs.getBoolPref("widget.non-native-theme.enabled");
611    } catch (e) {
612        sandbox.nativeThemePref = true;
613    }
614    sandbox.gpuProcessForceEnabled = prefs.getBoolPref("layers.gpu-process.force-enabled", false);
615
616    sandbox.prefs = Cu.cloneInto({
617        getBoolPref: function(p) { return prefs.getBoolPref(p); },
618        getIntPref:  function(p) { return prefs.getIntPref(p); }
619    }, sandbox, { cloneFunctions: true });
620
621    // Tests shouldn't care about this except for when they need to
622    // crash the content process
623    sandbox.browserIsRemote = g.browserIsRemote;
624    sandbox.browserIsFission = g.browserIsFission;
625
626    try {
627        sandbox.asyncPan = g.containingWindow.docShell.asyncPanZoomEnabled && !sandbox.useDrawSnapshot;
628    } catch (e) {
629        sandbox.asyncPan = false;
630    }
631
632    // Graphics features
633    sandbox.usesRepeatResampling = sandbox.d2d;
634
635    // Running in a test-verify session?
636    sandbox.verify = prefs.getBoolPref("reftest.verify", false);
637
638    // Running with a variant enabled?
639    sandbox.fission = Services.appinfo.fissionAutostart;
640    sandbox.serviceWorkerE10s = true;
641
642    if (!g.dumpedConditionSandbox) {
643        g.logger.info("Dumping JSON representation of sandbox");
644        g.logger.info(JSON.stringify(Cu.waiveXrays(sandbox)));
645        g.dumpedConditionSandbox = true;
646    }
647
648    return sandbox;
649}
650
651function AddRetainedDisplayListTestPrefs(aSandbox, aTestPrefSettings,
652                                         aRefPrefSettings) {
653    AddPrefSettings("test-", "layout.display-list.retain", "true", aSandbox,
654                    aTestPrefSettings, aRefPrefSettings);
655    AddPrefSettings("ref-", "layout.display-list.retain", "false", aSandbox,
656                    aTestPrefSettings, aRefPrefSettings);
657}
658
659function AddPrefSettings(aWhere, aPrefName, aPrefValExpression, aSandbox, aTestPrefSettings, aRefPrefSettings) {
660    var prefVal = Cu.evalInSandbox("(" + aPrefValExpression + ")", aSandbox);
661    var prefType;
662    var valType = typeof(prefVal);
663    if (valType == "boolean") {
664        prefType = PREF_BOOLEAN;
665    } else if (valType == "string") {
666        prefType = PREF_STRING;
667    } else if (valType == "number" && (parseInt(prefVal) == prefVal)) {
668        prefType = PREF_INTEGER;
669    } else {
670        return false;
671    }
672    var setting = { name: aPrefName,
673                    type: prefType,
674                    value: prefVal };
675
676    if (g.compareRetainedDisplayLists && aPrefName != "layout.display-list.retain") {
677        // ref-pref() is ignored, test-pref() and pref() are added to both
678        if (aWhere != "ref-") {
679            aTestPrefSettings.push(setting);
680            aRefPrefSettings.push(setting);
681        }
682    } else {
683        if (aWhere != "ref-") {
684            aTestPrefSettings.push(setting);
685        }
686        if (aWhere != "test-") {
687            aRefPrefSettings.push(setting);
688        }
689    }
690    return true;
691}
692
693function ExtractRange(matches, startIndex) {
694    return {
695        min: Number(matches[startIndex]),
696        max: Number(matches[startIndex + 1])
697    };
698}
699
700function ServeTestBase(aURL, depth) {
701    var listURL = aURL.QueryInterface(Ci.nsIFileURL);
702    var directory = listURL.file.parent;
703
704    // Allow serving a tree that's an ancestor of the directory containing
705    // the files so that they can use resources in ../ (etc.).
706    var dirPath = "/";
707    while (depth > 0) {
708        dirPath = "/" + directory.leafName + dirPath;
709        directory = directory.parent;
710        --depth;
711    }
712
713    g.count++;
714    var path = "/" + Date.now() + "/" + g.count;
715    g.server.registerDirectory(path + "/", directory);
716
717    var secMan = Cc[NS_SCRIPTSECURITYMANAGER_CONTRACTID]
718                     .getService(Ci.nsIScriptSecurityManager);
719
720    var testbase = g.ioService.newURI("http://localhost:" + g.httpServerPort +
721                                     path + dirPath);
722    var testBasePrincipal = secMan.createContentPrincipal(testbase, {});
723
724    // Give the testbase URI access to XUL and XBL
725    Services.perms.addFromPrincipal(testBasePrincipal, "allowXULXBL", Services.perms.ALLOW_ACTION);
726    return testbase;
727}
728
729function CreateUrls(test) {
730    let secMan = Cc[NS_SCRIPTSECURITYMANAGER_CONTRACTID]
731                    .getService(Ci.nsIScriptSecurityManager);
732
733    let manifestURL = g.ioService.newURI(test.manifest);
734
735    let testbase = manifestURL;
736    if (test.runHttp)
737        testbase = ServeTestBase(manifestURL, test.httpDepth)
738
739    function FileToURI(file)
740    {
741        if (file === null)
742            return file;
743
744        var testURI = g.ioService.newURI(file, null, testbase);
745        let isChromeOrViewSource = testURI.scheme == "chrome" || testURI.scheme == "view-source";
746        let principal = isChromeOrViewSource ? secMan.getSystemPrincipal() :
747                                               secMan.createContentPrincipal(manifestURL, {});
748        secMan.checkLoadURIWithPrincipal(principal, testURI,
749                                         Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
750        return testURI;
751    }
752
753    let files = [test.url1, test.url2];
754    [test.url1, test.url2] = files.map(FileToURI);
755
756    return test;
757}
758
759function TestIdentifier(aUrl, aManifestID) {
760    // Construct a platform-independent and location-independent test identifier for
761    // a url; normally the identifier looks like a posix-compliant relative file
762    // path.
763    // Test urls may be simple file names, chrome: urls with full paths, about:blank, etc.
764    if (aUrl.startsWith("about:") || aUrl.startsWith("data:")) {
765        return aUrl;
766    }
767    var pos = aUrl.lastIndexOf("/");
768    var url = (pos < 0) ? aUrl : aUrl.substring(pos + 1);
769    return (aManifestID + "/" + url);
770}
771
772function AddTestItem(aTest, aFilter, aManifestID) {
773    if (!aFilter)
774        aFilter = [null, [], false];
775
776    var identifier = TestIdentifier(aTest.url1, aManifestID);
777    if (aTest.url2 !== null) {
778        identifier = [identifier, aTest.type, TestIdentifier(aTest.url2, aManifestID)];
779    }
780
781    var {url1, url2} = CreateUrls(Object.assign({}, aTest));
782
783    var globalFilter = aFilter[0];
784    var manifestFilter = aFilter[1];
785    var invertManifest = aFilter[2];
786    if (globalFilter && !globalFilter.test(url1.spec))
787        return;
788    if (manifestFilter && !(invertManifest ^ manifestFilter.test(url1.spec)))
789        return;
790    if (g.focusFilterMode == FOCUS_FILTER_NEEDS_FOCUS_TESTS &&
791        !aTest.needsFocus)
792        return;
793    if (g.focusFilterMode == FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS &&
794        aTest.needsFocus)
795        return;
796
797    aTest.identifier = identifier;
798    g.urls.push(aTest);
799    // Periodically log progress to avoid no-output timeout on slow platforms.
800    // No-output timeouts during manifest parsing have been a problem for
801    // jsreftests on Android/debug. Any logging resets the no-output timer,
802    // even debug logging which is normally not displayed.
803    if ((g.urls.length % 5000) == 0)
804        g.logger.debug(g.urls.length + " tests found...");
805}
806