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("chrome://reftest/content/globals.jsm", this);
11Cu.import("chrome://reftest/content/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)
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);
31}
32
33// Note: If you materially change the reftest manifest parsing,
34// please keep the parser in print-manifest-dirs.py in sync.
35function ReadManifest(aURL, aFilter)
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, loadUsingSystemPrincipal: true});
53    var inputStream = channel.open2();
54    if (channel instanceof Ci.nsIHttpChannel
55        && channel.responseStatus != 200) {
56      g.logger.error("HTTP ERROR : " + channel.responseStatus);
57    }
58    var streamBuf = getStreamContent(inputStream);
59    inputStream.close();
60    var lines = streamBuf.split(/\n|\r|\r\n/);
61
62    // Build the sandbox for fails-if(), etc., condition evaluation.
63    var sandbox = BuildConditionSandbox(aURL);
64    var lineNo = 0;
65    var urlprefix = "";
66    var defaultTestPrefSettings = [], defaultRefPrefSettings = [];
67    if (g.compareRetainedDisplayLists) {
68        AddRetainedDisplayListTestPrefs(sandbox, defaultTestPrefSettings,
69                                        defaultRefPrefSettings);
70    }
71    if (g.compareStyloToGecko) {
72        AddStyloTestPrefs(sandbox, defaultTestPrefSettings,
73                          defaultRefPrefSettings);
74    }
75    for (var str of lines) {
76        ++lineNo;
77        if (str.charAt(0) == "#")
78            continue; // entire line was a comment
79        var i = str.search(/\s+#/);
80        if (i >= 0)
81            str = str.substring(0, i);
82        // strip leading and trailing whitespace
83        str = str.replace(/^\s*/, '').replace(/\s*$/, '');
84        if (!str || str == "")
85            continue;
86        var items = str.split(/\s+/); // split on whitespace
87
88        if (items[0] == "url-prefix") {
89            if (items.length != 2)
90                throw "url-prefix requires one url in manifest file " + aURL.spec + " line " + lineNo;
91            urlprefix = items[1];
92            continue;
93        }
94
95        if (items[0] == "default-preferences") {
96            var m;
97            var item;
98            defaultTestPrefSettings = [];
99            defaultRefPrefSettings = [];
100            items.shift();
101            while ((item = items.shift())) {
102                if (!(m = item.match(RE_PREF_ITEM))) {
103                    throw "Unexpected item in default-preferences list in manifest file " + aURL.spec + " line " + lineNo;
104                }
105                if (!AddPrefSettings(m[1], m[2], m[3], sandbox, defaultTestPrefSettings, defaultRefPrefSettings)) {
106                    throw "Error in pref value in manifest file " + aURL.spec + " line " + lineNo;
107                }
108            }
109            if (g.compareRetainedDisplayLists) {
110                AddRetainedDisplayListTestPrefs(sandbox, defaultTestPrefSettings,
111                                                defaultRefPrefSettings);
112            }
113            if (g.compareStyloToGecko) {
114                AddStyloTestPrefs(sandbox, defaultTestPrefSettings,
115                                  defaultRefPrefSettings);
116            }
117            continue;
118        }
119
120        var expected_status = EXPECTED_PASS;
121        var allow_silent_fail = false;
122        var minAsserts = 0;
123        var maxAsserts = 0;
124        var needs_focus = false;
125        var slow = false;
126        var testPrefSettings = defaultTestPrefSettings.concat();
127        var refPrefSettings = defaultRefPrefSettings.concat();
128        var fuzzy_delta = { min: 0, max: 2 };
129        var fuzzy_pixels = { min: 0, max: 1 };
130        var chaosMode = false;
131        var nonSkipUsed = false;
132
133        while (items[0].match(/^(fails|needs-focus|random|skip|asserts|slow|require-or|silentfail|pref|test-pref|ref-pref|fuzzy|chaos-mode)/)) {
134            var item = items.shift();
135            var stat;
136            var cond;
137            var m = item.match(/^(fails|random|skip|silentfail)-if(\(.*\))$/);
138            if (m) {
139                stat = m[1];
140                // Note: m[2] contains the parentheses, and we want them.
141                cond = Cu.evalInSandbox(m[2], sandbox);
142            } else if (item.match(/^(fails|random|skip)$/)) {
143                stat = item;
144                cond = true;
145            } else if (item == "needs-focus") {
146                needs_focus = true;
147                cond = false;
148            } else if ((m = item.match(/^asserts\((\d+)(-\d+)?\)$/))) {
149                cond = false;
150                minAsserts = Number(m[1]);
151                maxAsserts = (m[2] == undefined) ? minAsserts
152                                                 : Number(m[2].substring(1));
153            } else if ((m = item.match(/^asserts-if\((.*?),(\d+)(-\d+)?\)$/))) {
154                cond = false;
155                if (Cu.evalInSandbox("(" + m[1] + ")", sandbox)) {
156                    minAsserts = Number(m[2]);
157                    maxAsserts =
158                      (m[3] == undefined) ? minAsserts
159                                          : Number(m[3].substring(1));
160                }
161            } else if (item == "slow") {
162                cond = false;
163                slow = true;
164            } else if ((m = item.match(/^require-or\((.*?)\)$/))) {
165                var args = m[1].split(/,/);
166                if (args.length != 2) {
167                    throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": wrong number of args to require-or";
168                }
169                var [precondition_str, fallback_action] = args;
170                var preconditions = precondition_str.split(/&&/);
171                cond = false;
172                for (var precondition of preconditions) {
173                    if (precondition === "debugMode") {
174                        // Currently unimplemented. Requires asynchronous
175                        // JSD call + getting an event while no JS is running
176                        stat = fallback_action;
177                        cond = true;
178                        break;
179                    } else if (precondition === "true") {
180                        // For testing
181                    } else {
182                        // Unknown precondition. Assume it is unimplemented.
183                        stat = fallback_action;
184                        cond = true;
185                        break;
186                    }
187                }
188            } else if ((m = item.match(/^slow-if\((.*?)\)$/))) {
189                cond = false;
190                if (Cu.evalInSandbox("(" + m[1] + ")", sandbox))
191                    slow = true;
192            } else if (item == "silentfail") {
193                cond = false;
194                allow_silent_fail = true;
195            } else if ((m = item.match(RE_PREF_ITEM))) {
196                cond = false;
197                if (!AddPrefSettings(m[1], m[2], m[3], sandbox, testPrefSettings, refPrefSettings)) {
198                    throw "Error in pref value in manifest file " + aURL.spec + " line " + lineNo;
199                }
200            } else if ((m = item.match(/^fuzzy\((\d+)(-\d+)?,(\d+)(-\d+)?\)$/))) {
201              cond = false;
202              expected_status = EXPECTED_FUZZY;
203              fuzzy_delta = ExtractRange(m, 1);
204              fuzzy_pixels = ExtractRange(m, 3);
205            } else if ((m = item.match(/^fuzzy-if\((.*?),(\d+)(-\d+)?,(\d+)(-\d+)?\)$/))) {
206              cond = false;
207              if (Cu.evalInSandbox("(" + m[1] + ")", sandbox)) {
208                expected_status = EXPECTED_FUZZY;
209                fuzzy_delta = ExtractRange(m, 2);
210                fuzzy_pixels = ExtractRange(m, 4);
211              }
212            } else if (item == "chaos-mode") {
213                cond = false;
214                chaosMode = true;
215            } else {
216                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": unexpected item " + item;
217            }
218
219            if (stat != "skip") {
220                nonSkipUsed = true;
221            }
222
223            if (cond) {
224                if (stat == "fails") {
225                    expected_status = EXPECTED_FAIL;
226                } else if (stat == "random") {
227                    expected_status = EXPECTED_RANDOM;
228                } else if (stat == "skip") {
229                    expected_status = EXPECTED_DEATH;
230                } else if (stat == "silentfail") {
231                    allow_silent_fail = true;
232                }
233            }
234        }
235
236        if (minAsserts > maxAsserts) {
237            throw "Bad range in manifest file " + aURL.spec + " line " + lineNo;
238        }
239
240        var runHttp = false;
241        var httpDepth;
242        if (items[0] == "HTTP") {
243            runHttp = (aURL.scheme == "file"); // We can't yet run the local HTTP server
244                                               // for non-local reftests.
245            httpDepth = 0;
246            items.shift();
247        } else if (items[0].match(/HTTP\(\.\.(\/\.\.)*\)/)) {
248            // Accept HTTP(..), HTTP(../..), HTTP(../../..), etc.
249            runHttp = (aURL.scheme == "file"); // We can't yet run the local HTTP server
250                                               // for non-local reftests.
251            httpDepth = (items[0].length - 5) / 3;
252            items.shift();
253        }
254
255        // do not prefix the url for include commands or urls specifying
256        // a protocol
257        if (urlprefix && items[0] != "include") {
258            if (items.length > 1 && !items[1].match(RE_PROTOCOL)) {
259                items[1] = urlprefix + items[1];
260            }
261            if (items.length > 2 && !items[2].match(RE_PROTOCOL)) {
262                items[2] = urlprefix + items[2];
263            }
264        }
265
266        var principal = secMan.createCodebasePrincipal(aURL, {});
267
268        if (items[0] == "include") {
269            if (items.length != 2)
270                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to include";
271            if (runHttp)
272                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": use of include with http";
273
274            // If the expected_status is EXPECTED_PASS (the default) then allow
275            // the include. If it is EXPECTED_DEATH, that means there was a skip
276            // or skip-if annotation (with a true condition) on this include
277            // statement, so we should skip the include. Any other expected_status
278            // is disallowed since it's nonintuitive as to what the intended
279            // effect is.
280            if (nonSkipUsed) {
281                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": include statement with annotation other than 'skip' or 'skip-if'";
282            } else if (expected_status == EXPECTED_DEATH) {
283                g.logger.info("Skipping included manifest at " + aURL.spec + " line " + lineNo + " due to matching skip condition");
284            } else {
285                // poor man's assertion
286                if (expected_status != EXPECTED_PASS) {
287                    throw "Error in manifest file parsing code: we should never get expected_status=" + expected_status + " when nonSkipUsed=false (from " + aURL.spec + " line " + lineNo + ")";
288                }
289
290                var incURI = g.ioService.newURI(items[1], null, listURL);
291                secMan.checkLoadURIWithPrincipal(principal, incURI,
292                                                 Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
293                ReadManifest(incURI, aFilter);
294            }
295        } else if (items[0] == TYPE_LOAD || items[0] == TYPE_SCRIPT) {
296            if (items.length != 2)
297                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to " + items[0];
298            if (items[0] == TYPE_LOAD && expected_status != EXPECTED_PASS && expected_status != EXPECTED_DEATH)
299                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect known failure type for load test";
300            AddTestItem({ type: TYPE_LOAD,
301                          expected: expected_status,
302                          manifest: aURL.spec,
303                          allowSilentFail: allow_silent_fail,
304                          minAsserts: minAsserts,
305                          maxAsserts: maxAsserts,
306                          needsFocus: needs_focus,
307                          slow: slow,
308                          prefSettings1: testPrefSettings,
309                          prefSettings2: refPrefSettings,
310                          fuzzyMinDelta: fuzzy_delta.min,
311                          fuzzyMaxDelta: fuzzy_delta.max,
312                          fuzzyMinPixels: fuzzy_pixels.min,
313                          fuzzyMaxPixels: fuzzy_pixels.max,
314                          runHttp: runHttp,
315                          httpDepth: httpDepth,
316                          url1: items[1],
317                          url2: null,
318                          chaosMode: chaosMode }, aFilter);
319        } else if (items[0] == TYPE_REFTEST_EQUAL || items[0] == TYPE_REFTEST_NOTEQUAL || items[0] == TYPE_PRINT) {
320            if (items.length != 3)
321                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to " + items[0];
322
323            if (items[0] == TYPE_REFTEST_NOTEQUAL &&
324                expected_status == EXPECTED_FUZZY &&
325                (fuzzy_delta.min > 0 || fuzzy_pixels.min > 0)) {
326                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": minimum fuzz must be zero for tests of type " + items[0];
327            }
328
329            var type = items[0];
330            if (g.compareStyloToGecko || g.compareRetainedDisplayLists) {
331                type = TYPE_REFTEST_EQUAL;
332
333                // We expect twice as many assertion failures when running in
334                // styloVsGecko mode because we run each test twice: once in
335                // Stylo mode and once in Gecko mode.
336                minAsserts *= 2;
337                maxAsserts *= 2;
338
339                // Skip the test if it is expected to fail in both Stylo and
340                // Gecko modes. It would unexpectedly "pass" in styloVsGecko
341                // mode when comparing the two failures, which is not a useful
342                // result.
343                if (expected_status === EXPECTED_FAIL ||
344                    expected_status === EXPECTED_RANDOM) {
345                    expected_status = EXPECTED_DEATH;
346                }
347            }
348
349            AddTestItem({ type: type,
350                          expected: expected_status,
351                          manifest: aURL.spec,
352                          allowSilentFail: allow_silent_fail,
353                          minAsserts: minAsserts,
354                          maxAsserts: maxAsserts,
355                          needsFocus: needs_focus,
356                          slow: slow,
357                          prefSettings1: testPrefSettings,
358                          prefSettings2: refPrefSettings,
359                          fuzzyMinDelta: fuzzy_delta.min,
360                          fuzzyMaxDelta: fuzzy_delta.max,
361                          fuzzyMinPixels: fuzzy_pixels.min,
362                          fuzzyMaxPixels: fuzzy_pixels.max,
363                          runHttp: runHttp,
364                          httpDepth: httpDepth,
365                          url1: items[1],
366                          url2: items[2],
367                          chaosMode: chaosMode }, aFilter);
368        } else {
369            throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": unknown test type " + items[0];
370        }
371    }
372}
373
374// Read all available data from an input stream and return it
375// as a string.
376function getStreamContent(inputStream)
377{
378    var streamBuf = "";
379    var sis = Cc["@mozilla.org/scriptableinputstream;1"].
380                  createInstance(Ci.nsIScriptableInputStream);
381    sis.init(inputStream);
382
383    var available;
384    while ((available = sis.available()) != 0) {
385        streamBuf += sis.read(available);
386    }
387
388    return streamBuf;
389}
390
391// Build the sandbox for fails-if(), etc., condition evaluation.
392function BuildConditionSandbox(aURL) {
393    var sandbox = new Cu.Sandbox(aURL.spec);
394    var xr = Cc[NS_XREAPPINFO_CONTRACTID].getService(Ci.nsIXULRuntime);
395    var appInfo = Cc[NS_XREAPPINFO_CONTRACTID].getService(Ci.nsIXULAppInfo);
396    sandbox.isDebugBuild = g.debug.isDebugBuild;
397    var prefs = Cc["@mozilla.org/preferences-service;1"].
398                getService(Ci.nsIPrefBranch);
399    var env = Cc["@mozilla.org/process/environment;1"].
400                getService(Ci.nsIEnvironment);
401
402    sandbox.xulRuntime = Cu.cloneInto({widgetToolkit: xr.widgetToolkit, OS: xr.OS, XPCOMABI: xr.XPCOMABI}, sandbox);
403
404    var testRect = g.browser.getBoundingClientRect();
405    sandbox.smallScreen = false;
406    if (g.containingWindow.innerWidth < 800 || g.containingWindow.innerHeight < 1000) {
407        sandbox.smallScreen = true;
408    }
409
410    var gfxInfo = (NS_GFXINFO_CONTRACTID in Cc) && Cc[NS_GFXINFO_CONTRACTID].getService(Ci.nsIGfxInfo);
411    let readGfxInfo = function (obj, key) {
412      if (g.contentGfxInfo && (key in g.contentGfxInfo)) {
413        return g.contentGfxInfo[key];
414      }
415      return obj[key];
416    }
417
418    try {
419      sandbox.d2d = readGfxInfo(gfxInfo, "D2DEnabled");
420      sandbox.dwrite = readGfxInfo(gfxInfo, "DWriteEnabled");
421    } catch (e) {
422      sandbox.d2d = false;
423      sandbox.dwrite = false;
424    }
425
426    var info = gfxInfo.getInfo();
427    var canvasBackend = readGfxInfo(info, "AzureCanvasBackend");
428    var contentBackend = readGfxInfo(info, "AzureContentBackend");
429    var canvasAccelerated = readGfxInfo(info, "AzureCanvasAccelerated");
430
431    sandbox.gpuProcess = gfxInfo.usingGPUProcess;
432    sandbox.azureCairo = canvasBackend == "cairo";
433    sandbox.azureSkia = canvasBackend == "skia";
434    sandbox.skiaContent = contentBackend == "skia";
435    sandbox.azureSkiaGL = canvasAccelerated; // FIXME: assumes GL right now
436    // true if we are using the same Azure backend for rendering canvas and content
437    sandbox.contentSameGfxBackendAsCanvas = contentBackend == canvasBackend
438                                            || (contentBackend == "none" && canvasBackend == "cairo");
439
440    sandbox.layersGPUAccelerated =
441      g.windowUtils.layerManagerType != "Basic";
442    sandbox.d3d11 =
443      g.windowUtils.layerManagerType == "Direct3D 11";
444    sandbox.d3d9 =
445      g.windowUtils.layerManagerType == "Direct3D 9";
446    sandbox.layersOpenGL =
447      g.windowUtils.layerManagerType == "OpenGL";
448    sandbox.webrender =
449      g.windowUtils.layerManagerType == "WebRender";
450    sandbox.layersOMTC =
451      g.windowUtils.layerManagerRemote == true;
452    sandbox.advancedLayers =
453      g.windowUtils.usingAdvancedLayers == true;
454    sandbox.layerChecksEnabled = !sandbox.webrender;
455
456    sandbox.retainedDisplayList =
457      prefs.getBoolPref("layout.display-list.retain");
458
459    // Shortcuts for widget toolkits.
460    sandbox.Android = xr.OS == "Android";
461    sandbox.cocoaWidget = xr.widgetToolkit == "cocoa";
462    sandbox.gtkWidget = xr.widgetToolkit == "gtk3";
463    sandbox.qtWidget = xr.widgetToolkit == "qt";
464    sandbox.winWidget = xr.widgetToolkit == "windows";
465
466    // Scrollbars that are semi-transparent. See bug 1169666.
467    sandbox.transparentScrollbars = xr.widgetToolkit == "gtk3";
468
469    if (sandbox.Android) {
470        var sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
471
472        // This is currently used to distinguish Android 4.0.3 (SDK version 15)
473        // and later from Android 2.x
474        sandbox.AndroidVersion = sysInfo.getPropertyAsInt32("version");
475    }
476
477#if MOZ_ASAN
478    sandbox.AddressSanitizer = true;
479#else
480    sandbox.AddressSanitizer = false;
481#endif
482
483#if MOZ_WEBRTC
484    sandbox.webrtc = true;
485#else
486    sandbox.webrtc = false;
487#endif
488
489let retainedDisplayListsEnabled = prefs.getBoolPref("layout.display-list.retain", false);
490sandbox.retainedDisplayLists = retainedDisplayListsEnabled && !g.compareRetainedDisplayLists;
491sandbox.compareRetainedDisplayLists = g.compareRetainedDisplayLists;
492
493#ifdef MOZ_STYLO
494    let styloEnabled = false;
495    // Perhaps a bit redundant in places, but this is easier to compare with the
496    // the real check in `nsLayoutUtils.cpp` to ensure they test the same way.
497    if (env.get("STYLO_FORCE_ENABLED")) {
498        styloEnabled = true;
499    } else if (env.get("STYLO_FORCE_DISABLED")) {
500        styloEnabled = false;
501    } else {
502        styloEnabled = prefs.getBoolPref("layout.css.servo.enabled", false);
503    }
504    sandbox.stylo = styloEnabled && !g.compareStyloToGecko;
505    sandbox.styloVsGecko = g.compareStyloToGecko;
506#else
507    sandbox.stylo = false;
508    sandbox.styloVsGecko = false;
509#endif
510
511    sandbox.skiaPdf = false;
512
513#ifdef RELEASE_OR_BETA
514    sandbox.release_or_beta = true;
515#else
516    sandbox.release_or_beta = false;
517#endif
518
519    var hh = Cc[NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX + "http"].
520                 getService(Ci.nsIHttpProtocolHandler);
521    var httpProps = ["userAgent", "appName", "appVersion", "vendor",
522                     "vendorSub", "product", "productSub", "platform",
523                     "oscpu", "language", "misc"];
524    sandbox.http = new sandbox.Object();
525    httpProps.forEach((x) => sandbox.http[x] = hh[x]);
526
527    // Set OSX to be the Mac OS X version, as an integer, or undefined
528    // for other platforms.  The integer is formed by 100 times the
529    // major version plus the minor version, so 1006 for 10.6, 1010 for
530    // 10.10, etc.
531    var osxmatch = /Mac OS X (\d+).(\d+)$/.exec(hh.oscpu);
532    sandbox.OSX = osxmatch ? parseInt(osxmatch[1]) * 100 + parseInt(osxmatch[2]) : undefined;
533
534    // see if we have the test plugin available,
535    // and set a sandox prop accordingly
536    sandbox.haveTestPlugin = !sandbox.Android && !!getTestPlugin("Test Plug-in");
537
538    // Set a flag on sandbox if the windows default theme is active
539    sandbox.windowsDefaultTheme = g.containingWindow.matchMedia("(-moz-windows-default-theme)").matches;
540
541    try {
542        sandbox.nativeThemePref = !prefs.getBoolPref("mozilla.widget.disable-native-theme");
543    } catch (e) {
544        sandbox.nativeThemePref = true;
545    }
546    sandbox.gpuProcessForceEnabled = prefs.getBoolPref("layers.gpu-process.force-enabled", false);
547
548    sandbox.prefs = Cu.cloneInto({
549        getBoolPref: function(p) { return prefs.getBoolPref(p); },
550        getIntPref:  function(p) { return prefs.getIntPref(p); }
551    }, sandbox, { cloneFunctions: true });
552
553    // Tests shouldn't care about this except for when they need to
554    // crash the content process
555    sandbox.browserIsRemote = g.browserIsRemote;
556
557    try {
558        sandbox.asyncPan = g.containingWindow.document.docShell.asyncPanZoomEnabled;
559    } catch (e) {
560        sandbox.asyncPan = false;
561    }
562
563    // Graphics features
564    sandbox.usesRepeatResampling = sandbox.d2d;
565
566    // Running in a test-verify session?
567    sandbox.verify = prefs.getBoolPref("reftest.verify", false);
568
569    if (!g.dumpedConditionSandbox) {
570        g.logger.info("Dumping JSON representation of sandbox");
571        g.logger.info(JSON.stringify(Cu.waiveXrays(sandbox)));
572        g.dumpedConditionSandbox = true;
573    }
574
575    return sandbox;
576}
577
578function AddRetainedDisplayListTestPrefs(aSandbox, aTestPrefSettings,
579                                         aRefPrefSettings) {
580    AddPrefSettings("test-", "layout.display-list.retain", "true", aSandbox,
581                    aTestPrefSettings, aRefPrefSettings);
582    AddPrefSettings("ref-", "layout.display-list.retain", "false", aSandbox,
583                    aTestPrefSettings, aRefPrefSettings);
584}
585
586function AddStyloTestPrefs(aSandbox, aTestPrefSettings, aRefPrefSettings) {
587    AddPrefSettings("test-", "layout.css.servo.enabled", "true", aSandbox,
588                    aTestPrefSettings, aRefPrefSettings);
589    AddPrefSettings("ref-", "layout.css.servo.enabled", "false", aSandbox,
590                    aTestPrefSettings, aRefPrefSettings);
591}
592
593function AddPrefSettings(aWhere, aPrefName, aPrefValExpression, aSandbox, aTestPrefSettings, aRefPrefSettings) {
594    var prefVal = Cu.evalInSandbox("(" + aPrefValExpression + ")", aSandbox);
595    var prefType;
596    var valType = typeof(prefVal);
597    if (valType == "boolean") {
598        prefType = PREF_BOOLEAN;
599    } else if (valType == "string") {
600        prefType = PREF_STRING;
601    } else if (valType == "number" && (parseInt(prefVal) == prefVal)) {
602        prefType = PREF_INTEGER;
603    } else {
604        return false;
605    }
606    var setting = { name: aPrefName,
607                    type: prefType,
608                    value: prefVal };
609
610    if ((g.compareStyloToGecko && aPrefName != "layout.css.servo.enabled") ||
611        (g.compareRetainedDisplayLists && aPrefName != "layout.display-list.retain")) {
612        // ref-pref() is ignored, test-pref() and pref() are added to both
613        if (aWhere != "ref-") {
614            aTestPrefSettings.push(setting);
615            aRefPrefSettings.push(setting);
616        }
617    } else {
618        if (aWhere != "ref-") {
619            aTestPrefSettings.push(setting);
620        }
621        if (aWhere != "test-") {
622            aRefPrefSettings.push(setting);
623        }
624    }
625    return true;
626}
627
628function ExtractRange(matches, startIndex, defaultMin = 0) {
629    if (matches[startIndex + 1] === undefined) {
630        return {
631            min: defaultMin,
632            max: Number(matches[startIndex])
633        };
634    }
635    return {
636        min: Number(matches[startIndex]),
637        max: Number(matches[startIndex + 1].substring(1))
638    };
639}
640
641function ServeTestBase(aURL, depth) {
642    var listURL = aURL.QueryInterface(Ci.nsIFileURL);
643    var directory = listURL.file.parent;
644
645    // Allow serving a tree that's an ancestor of the directory containing
646    // the files so that they can use resources in ../ (etc.).
647    var dirPath = "/";
648    while (depth > 0) {
649        dirPath = "/" + directory.leafName + dirPath;
650        directory = directory.parent;
651        --depth;
652    }
653
654    g.count++;
655    var path = "/" + Date.now() + "/" + g.count;
656    g.server.registerDirectory(path + "/", directory);
657
658    var secMan = Cc[NS_SCRIPTSECURITYMANAGER_CONTRACTID]
659                     .getService(Ci.nsIScriptSecurityManager);
660
661    var testbase = g.ioService.newURI("http://localhost:" + g.httpServerPort +
662                                     path + dirPath);
663
664    // Give the testbase URI access to XUL and XBL
665    Services.perms.add(testbase, "allowXULXBL", Services.perms.ALLOW_ACTION);
666    return testbase;
667}
668
669function CreateUrls(test) {
670    let secMan = Cc[NS_SCRIPTSECURITYMANAGER_CONTRACTID]
671                    .getService(Ci.nsIScriptSecurityManager);
672
673    let manifestURL = g.ioService.newURI(test.manifest);
674    let principal = secMan.createCodebasePrincipal(manifestURL, {});
675
676    let testbase = manifestURL;
677    if (test.runHttp)
678        testbase = ServeTestBase(manifestURL, test.httpDepth)
679
680    function FileToURI(file)
681    {
682        if (file === null)
683            return file;
684
685        var testURI = g.ioService.newURI(file, null, testbase);
686        secMan.checkLoadURIWithPrincipal(principal, testURI,
687                                         Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
688        return testURI;
689    }
690
691    let files = [test.url1, test.url2];
692    [test.url1, test.url2] = files.map(FileToURI);
693    if (test.url2 && g.compareStyloToGecko)
694        test.url2 = test.url1;
695
696    return test;
697}
698
699function AddTestItem(aTest, aFilter) {
700    if (!aFilter)
701        aFilter = [null, [], false];
702
703    var {url1, url2} = CreateUrls(Object.assign({}, aTest));
704
705    var globalFilter = aFilter[0];
706    var manifestFilter = aFilter[1];
707    var invertManifest = aFilter[2];
708    if ((globalFilter && !globalFilter.test(url1.spec)) ||
709        (manifestFilter &&
710         !(invertManifest ^ manifestFilter.test(url1.spec))))
711        return;
712    if (g.focusFilterMode == FOCUS_FILTER_NEEDS_FOCUS_TESTS &&
713        !aTest.needsFocus)
714        return;
715    if (g.focusFilterMode == FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS &&
716        aTest.needsFocus)
717        return;
718
719    if (url2 !== null)
720        aTest.identifier = [url1.spec, aTest.type, url2.spec];
721    else
722        aTest.identifier = url1.spec;
723    g.urls.push(aTest);
724}
725