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
7this.EXPORTED_SYMBOLS = ["OnRefTestLoad", "OnRefTestUnload"];
8
9var CC = Components.classes;
10const CI = Components.interfaces;
11const CR = Components.results;
12const CU = Components.utils;
13
14const XHTML_NS = "http://www.w3.org/1999/xhtml";
15const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
16
17const NS_LOCAL_FILE_CONTRACTID = "@mozilla.org/file/local;1";
18const NS_GFXINFO_CONTRACTID = "@mozilla.org/gfx/info;1";
19const IO_SERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
20const DEBUG_CONTRACTID = "@mozilla.org/xpcom/debug;1";
21const NS_LOCALFILEINPUTSTREAM_CONTRACTID =
22          "@mozilla.org/network/file-input-stream;1";
23const NS_SCRIPTSECURITYMANAGER_CONTRACTID =
24          "@mozilla.org/scriptsecuritymanager;1";
25const NS_REFTESTHELPER_CONTRACTID =
26          "@mozilla.org/reftest-helper;1";
27const NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX =
28          "@mozilla.org/network/protocol;1?name=";
29const NS_XREAPPINFO_CONTRACTID =
30          "@mozilla.org/xre/app-info;1";
31const NS_DIRECTORY_SERVICE_CONTRACTID =
32          "@mozilla.org/file/directory_service;1";
33const NS_OBSERVER_SERVICE_CONTRACTID =
34          "@mozilla.org/observer-service;1";
35
36CU.import("resource://gre/modules/FileUtils.jsm");
37CU.import("chrome://reftest/content/httpd.jsm", this);
38CU.import("chrome://reftest/content/StructuredLog.jsm", this);
39CU.import("resource://gre/modules/Services.jsm");
40CU.import("resource://gre/modules/NetUtil.jsm");
41
42var gLoadTimeout = 0;
43var gTimeoutHook = null;
44var gRemote = false;
45var gIgnoreWindowSize = false;
46var gShuffle = false;
47var gRepeat = null;
48var gRunUntilFailure = false;
49var gTotalChunks = 0;
50var gThisChunk = 0;
51var gContainingWindow = null;
52var gURLFilterRegex = {};
53var gContentGfxInfo = null;
54const FOCUS_FILTER_ALL_TESTS = "all";
55const FOCUS_FILTER_NEEDS_FOCUS_TESTS = "needs-focus";
56const FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS = "non-needs-focus";
57var gFocusFilterMode = FOCUS_FILTER_ALL_TESTS;
58
59// "<!--CLEAR-->"
60const BLANK_URL_FOR_CLEARING = "data:text/html;charset=UTF-8,%3C%21%2D%2DCLEAR%2D%2D%3E";
61
62var gBrowser;
63// Are we testing web content loaded in a separate process?
64var gBrowserIsRemote;           // bool
65var gB2GisMulet;                // bool
66// Are we using <iframe mozbrowser>?
67var gBrowserIsIframe;           // bool
68var gBrowserMessageManager;
69var gCanvas1, gCanvas2;
70// gCurrentCanvas is non-null between InitCurrentCanvasWithSnapshot and the next
71// RecordResult.
72var gCurrentCanvas = null;
73var gURLs;
74var gManifestsLoaded = {};
75// Map from URI spec to the number of times it remains to be used
76var gURIUseCounts;
77// Map from URI spec to the canvas rendered for that URI
78var gURICanvases;
79var gTestResults = {
80  // Successful...
81  Pass: 0,
82  LoadOnly: 0,
83  // Unexpected...
84  Exception: 0,
85  FailedLoad: 0,
86  UnexpectedFail: 0,
87  UnexpectedPass: 0,
88  AssertionUnexpected: 0,
89  AssertionUnexpectedFixed: 0,
90  // Known problems...
91  KnownFail : 0,
92  AssertionKnown: 0,
93  Random : 0,
94  Skip: 0,
95  Slow: 0,
96};
97var gTotalTests = 0;
98var gState;
99var gCurrentURL;
100var gTestLog = [];
101var gLogLevel;
102var gServer;
103var gCount = 0;
104var gAssertionCount = 0;
105
106var gIOService;
107var gDebug;
108var gWindowUtils;
109
110var gSlowestTestTime = 0;
111var gSlowestTestURL;
112var gFailedUseWidgetLayers = false;
113
114var gDrawWindowFlags;
115
116var gExpectingProcessCrash = false;
117var gExpectedCrashDumpFiles = [];
118var gUnexpectedCrashDumpFiles = { };
119var gCrashDumpDir;
120var gFailedNoPaint = false;
121var gFailedOpaqueLayer = false;
122var gFailedOpaqueLayerMessages = [];
123var gFailedAssignedLayer = false;
124var gFailedAssignedLayerMessages = [];
125
126// The enabled-state of the test-plugins, stored so they can be reset later
127var gTestPluginEnabledStates = null;
128
129const TYPE_REFTEST_EQUAL = '==';
130const TYPE_REFTEST_NOTEQUAL = '!=';
131const TYPE_LOAD = 'load';     // test without a reference (just test that it does
132                              // not assert, crash, hang, or leak)
133const TYPE_SCRIPT = 'script'; // test contains individual test results
134
135// The order of these constants matters, since when we have a status
136// listed for a *manifest*, we combine the status with the status for
137// the test by using the *larger*.
138// FIXME: In the future, we may also want to use this rule for combining
139// statuses that are on the same line (rather than making the last one
140// win).
141const EXPECTED_PASS = 0;
142const EXPECTED_FAIL = 1;
143const EXPECTED_RANDOM = 2;
144const EXPECTED_DEATH = 3;  // test must be skipped to avoid e.g. crash/hang
145const EXPECTED_FUZZY = 4;
146
147// types of preference value we might want to set for a specific test
148const PREF_BOOLEAN = 0;
149const PREF_STRING  = 1;
150const PREF_INTEGER = 2;
151
152var gPrefsToRestore = [];
153
154const gProtocolRE = /^\w+:/;
155const gPrefItemRE = /^(|test-|ref-)pref\((.+?),(.*)\)$/;
156
157var gHttpServerPort = -1;
158
159// whether to run slow tests or not
160var gRunSlowTests = true;
161
162// whether we should skip caching canvases
163var gNoCanvasCache = false;
164
165var gRecycledCanvases = new Array();
166
167// Only dump the sandbox once, because it doesn't depend on the
168// manifest URL (yet!).
169var gDumpedConditionSandbox = false;
170
171function HasUnexpectedResult()
172{
173    return gTestResults.Exception > 0 ||
174           gTestResults.FailedLoad > 0 ||
175           gTestResults.UnexpectedFail > 0 ||
176           gTestResults.UnexpectedPass > 0 ||
177           gTestResults.AssertionUnexpected > 0 ||
178           gTestResults.AssertionUnexpectedFixed > 0;
179}
180
181// By default we just log to stdout
182var gLogFile = null;
183var gDumpFn = function(line) {
184  dump(line);
185  if (gLogFile) {
186    gLogFile.write(line, line.length);
187  }
188}
189var gDumpRawLog = function(record) {
190  // Dump JSON representation of data on a single line
191  var line = "\n" + JSON.stringify(record) + "\n";
192  dump(line);
193
194  if (gLogFile) {
195    gLogFile.write(line, line.length);
196  }
197}
198var logger = new StructuredLogger('reftest', gDumpRawLog);
199
200function TestBuffer(str)
201{
202  logger.debug(str);
203  gTestLog.push(str);
204}
205
206function FlushTestBuffer()
207{
208  // In debug mode, we've dumped all these messages already.
209  if (gLogLevel !== 'debug') {
210    for (var i = 0; i < gTestLog.length; ++i) {
211      logger.info("Saved log: " + gTestLog[i]);
212    }
213  }
214  gTestLog = [];
215}
216
217function LogWidgetLayersFailure()
218{
219  logger.error("USE_WIDGET_LAYERS disabled because the screen resolution is too low. This falls back to an alternate rendering path (that may not be representative) and is not implemented with e10s enabled.");
220  logger.error("Consider increasing your screen resolution, or adding '--disable-e10s' to your './mach reftest' command");
221}
222
223function AllocateCanvas()
224{
225    if (gRecycledCanvases.length > 0) {
226        return gRecycledCanvases.shift();
227    }
228
229    var canvas = gContainingWindow.document.createElementNS(XHTML_NS, "canvas");
230    var r = gBrowser.getBoundingClientRect();
231    canvas.setAttribute("width", Math.ceil(r.width));
232    canvas.setAttribute("height", Math.ceil(r.height));
233
234    return canvas;
235}
236
237function ReleaseCanvas(canvas)
238{
239    // store a maximum of 2 canvases, if we're not caching
240    if (!gNoCanvasCache || gRecycledCanvases.length < 2) {
241        gRecycledCanvases.push(canvas);
242    }
243}
244
245function IDForEventTarget(event)
246{
247    try {
248        return "'" + event.target.getAttribute('id') + "'";
249    } catch (ex) {
250        return "<unknown>";
251    }
252}
253
254function getTestPlugin(aName) {
255  var ph = CC["@mozilla.org/plugin/host;1"].getService(CI.nsIPluginHost);
256  var tags = ph.getPluginTags();
257
258  // Find the test plugin
259  for (var i = 0; i < tags.length; i++) {
260    if (tags[i].name == aName)
261      return tags[i];
262  }
263
264  logger.warning("Failed to find the test-plugin.");
265  return null;
266}
267
268this.OnRefTestLoad = function OnRefTestLoad(win)
269{
270    gCrashDumpDir = CC[NS_DIRECTORY_SERVICE_CONTRACTID]
271                    .getService(CI.nsIProperties)
272                    .get("ProfD", CI.nsIFile);
273    gCrashDumpDir.append("minidumps");
274
275    var env = CC["@mozilla.org/process/environment;1"].
276              getService(CI.nsIEnvironment);
277
278    var prefs = Components.classes["@mozilla.org/preferences-service;1"].
279                getService(Components.interfaces.nsIPrefBranch);
280    try {
281        gBrowserIsRemote = prefs.getBoolPref("browser.tabs.remote.autostart");
282    } catch (e) {
283        gBrowserIsRemote = false;
284    }
285
286    try {
287        gB2GisMulet = prefs.getBoolPref("b2g.is_mulet");
288    } catch (e) {
289        gB2GisMulet = false;
290    }
291
292    try {
293      gBrowserIsIframe = prefs.getBoolPref("reftest.browser.iframe.enabled");
294    } catch (e) {
295      gBrowserIsIframe = false;
296    }
297
298    try {
299      gLogLevel = prefs.getCharPref("reftest.logLevel");
300    } catch (e) {
301      gLogLevel ='info';
302    }
303
304    if (win === undefined || win == null) {
305      win = window;
306    }
307    if (gContainingWindow == null && win != null) {
308      gContainingWindow = win;
309    }
310
311    if (gBrowserIsIframe) {
312      gBrowser = gContainingWindow.document.createElementNS(XHTML_NS, "iframe");
313      gBrowser.setAttribute("mozbrowser", "");
314      gBrowser.setAttribute("mozapp", prefs.getCharPref("b2g.system_manifest_url"));
315    } else {
316      gBrowser = gContainingWindow.document.createElementNS(XUL_NS, "xul:browser");
317    }
318    gBrowser.setAttribute("id", "browser");
319    gBrowser.setAttribute("type", "content-primary");
320    gBrowser.setAttribute("remote", gBrowserIsRemote ? "true" : "false");
321    // Make sure the browser element is exactly 800x1000, no matter
322    // what size our window is
323    gBrowser.setAttribute("style", "padding: 0px; margin: 0px; border:none; min-width: 800px; min-height: 1000px; max-width: 800px; max-height: 1000px");
324
325    if (Services.appinfo.OS == "Android") {
326      let doc;
327      if (Services.appinfo.widgetToolkit == "gonk") {
328        doc = gContainingWindow.document.getElementsByTagName("html")[0];
329      } else {
330        doc = gContainingWindow.document.getElementById('main-window');
331      }
332      while (doc.hasChildNodes()) {
333        doc.removeChild(doc.firstChild);
334      }
335      doc.appendChild(gBrowser);
336    } else {
337      document.getElementById("reftest-window").appendChild(gBrowser);
338    }
339
340    // reftests should have the test plugins enabled, not click-to-play
341    let plugin1 = getTestPlugin("Test Plug-in");
342    let plugin2 = getTestPlugin("Second Test Plug-in");
343    if (plugin1 && plugin2) {
344      gTestPluginEnabledStates = [plugin1.enabledState, plugin2.enabledState];
345      plugin1.enabledState = CI.nsIPluginTag.STATE_ENABLED;
346      plugin2.enabledState = CI.nsIPluginTag.STATE_ENABLED;
347    } else {
348      logger.warning("Could not get test plugin tags.");
349    }
350
351    gBrowserMessageManager = gBrowser.QueryInterface(CI.nsIFrameLoaderOwner)
352                                     .frameLoader.messageManager;
353    // The content script waits for the initial onload, then notifies
354    // us.
355    RegisterMessageListenersAndLoadContentScript();
356}
357
358function InitAndStartRefTests()
359{
360    /* These prefs are optional, so we don't need to spit an error to the log */
361    try {
362        var prefs = Components.classes["@mozilla.org/preferences-service;1"].
363                    getService(Components.interfaces.nsIPrefBranch);
364    } catch(e) {
365        logger.error("EXCEPTION: " + e);
366    }
367
368    try {
369      prefs.setBoolPref("android.widget_paints_background", false);
370    } catch (e) {}
371
372    /* set the gLoadTimeout */
373    try {
374        gLoadTimeout = prefs.getIntPref("reftest.timeout");
375    } catch(e) {
376        gLoadTimeout = 5 * 60 * 1000; //5 minutes as per bug 479518
377    }
378
379    /* Get the logfile for android tests */
380    try {
381        var logFile = prefs.getCharPref("reftest.logFile");
382        if (logFile) {
383            var f = FileUtils.File(logFile);
384            gLogFile = FileUtils.openFileOutputStream(f, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE);
385        }
386    } catch(e) {}
387
388    try {
389        gRemote = prefs.getBoolPref("reftest.remote");
390    } catch(e) {
391        gRemote = false;
392    }
393
394    try {
395        gIgnoreWindowSize = prefs.getBoolPref("reftest.ignoreWindowSize");
396    } catch(e) {
397        gIgnoreWindowSize = false;
398    }
399
400    /* Support for running a chunk (subset) of tests.  In separate try as this is optional */
401    try {
402        gTotalChunks = prefs.getIntPref("reftest.totalChunks");
403        gThisChunk = prefs.getIntPref("reftest.thisChunk");
404    }
405    catch(e) {
406        gTotalChunks = 0;
407        gThisChunk = 0;
408    }
409
410    try {
411        gFocusFilterMode = prefs.getCharPref("reftest.focusFilterMode");
412    } catch(e) {}
413
414    gWindowUtils = gContainingWindow.QueryInterface(CI.nsIInterfaceRequestor).getInterface(CI.nsIDOMWindowUtils);
415    if (!gWindowUtils || !gWindowUtils.compareCanvases)
416        throw "nsIDOMWindowUtils inteface missing";
417
418    gIOService = CC[IO_SERVICE_CONTRACTID].getService(CI.nsIIOService);
419    gDebug = CC[DEBUG_CONTRACTID].getService(CI.nsIDebug2);
420
421    RegisterProcessCrashObservers();
422
423    if (gRemote) {
424        gServer = null;
425    } else {
426        gServer = new HttpServer();
427    }
428    try {
429        if (gServer)
430            StartHTTPServer();
431    } catch (ex) {
432        //gBrowser.loadURI('data:text/plain,' + ex);
433        ++gTestResults.Exception;
434        logger.error("EXCEPTION: " + ex);
435        DoneTests();
436    }
437
438    // Focus the content browser.
439    if (gFocusFilterMode != FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS) {
440        gBrowser.focus();
441    }
442
443    StartTests();
444}
445
446function StartHTTPServer()
447{
448    gServer.registerContentType("sjs", "sjs");
449    gServer.start(-1);
450    gHttpServerPort = gServer.identity.primaryPort;
451}
452
453// Perform a Fisher-Yates shuffle of the array.
454function Shuffle(array)
455{
456    for (var i = array.length - 1; i > 0; i--) {
457        var j = Math.floor(Math.random() * (i + 1));
458        var temp = array[i];
459        array[i] = array[j];
460        array[j] = temp;
461    }
462}
463
464function StartTests()
465{
466    var manifests;
467    /* These prefs are optional, so we don't need to spit an error to the log */
468    try {
469        var prefs = Components.classes["@mozilla.org/preferences-service;1"].
470                    getService(Components.interfaces.nsIPrefBranch);
471    } catch(e) {
472        logger.error("EXCEPTION: " + e);
473    }
474
475    try {
476        gNoCanvasCache = prefs.getIntPref("reftest.nocache");
477    } catch(e) {
478        gNoCanvasCache = false;
479    }
480
481    try {
482      gShuffle = prefs.getBoolPref("reftest.shuffle");
483    } catch (e) {
484      gShuffle = false;
485    }
486
487    try {
488      gRunUntilFailure = prefs.getBoolPref("reftest.runUntilFailure");
489    } catch (e) {
490      gRunUntilFailure = false;
491    }
492
493    // When we repeat this function is called again, so really only want to set
494    // gRepeat once.
495    if (gRepeat == null) {
496      try {
497        gRepeat = prefs.getIntPref("reftest.repeat");
498      } catch (e) {
499        gRepeat = 0;
500      }
501    }
502
503    try {
504        gRunSlowTests = prefs.getIntPref("reftest.skipslowtests");
505    } catch(e) {
506        gRunSlowTests = false;
507    }
508
509    if (gShuffle) {
510        gNoCanvasCache = true;
511    }
512
513    gURLs = [];
514    gManifestsLoaded = {};
515
516    try {
517        var manifests = JSON.parse(prefs.getCharPref("reftest.manifests"));
518        gURLFilterRegex = manifests[null];
519    } catch(e) {
520        logger.error("Unable to find reftest.manifests pref.  Please ensure your profile is setup properly");
521        DoneTests();
522    }
523
524    try {
525        var globalFilter = manifests.hasOwnProperty("") ? new RegExp(manifests[""]) : null;
526        var manifestURLs = Object.keys(manifests);
527
528        // Ensure we read manifests from higher up the directory tree first so that we
529        // process includes before reading the included manifest again
530        manifestURLs.sort(function(a,b) {return a.length - b.length})
531        manifestURLs.forEach(function(manifestURL) {
532            logger.info("Reading manifest " + manifestURL);
533            var filter = manifests[manifestURL] ? new RegExp(manifests[manifestURL]) : null;
534            ReadTopManifest(manifestURL, [globalFilter, filter, false]);
535        });
536        BuildUseCounts();
537
538        // Filter tests which will be skipped to get a more even distribution when chunking
539        // tURLs is a temporary array containing all active tests
540        var tURLs = new Array();
541        var tIDs = new Array();
542        for (var i = 0; i < gURLs.length; ++i) {
543            if (gURLs[i].expected == EXPECTED_DEATH)
544                continue;
545
546            if (gURLs[i].needsFocus && !Focus())
547                continue;
548
549            if (gURLs[i].slow && !gRunSlowTests)
550                continue;
551
552            tURLs.push(gURLs[i]);
553            tIDs.push(gURLs[i].identifier);
554        }
555
556        logger.suiteStart(tIDs, {"skipped": gURLs.length - tURLs.length});
557
558        if (gTotalChunks > 0 && gThisChunk > 0) {
559            // Calculate start and end indices of this chunk if tURLs array were
560            // divided evenly
561            var testsPerChunk = tURLs.length / gTotalChunks;
562            var start = Math.round((gThisChunk-1) * testsPerChunk);
563            var end = Math.round(gThisChunk * testsPerChunk);
564
565            // Map these indices onto the gURLs array. This avoids modifying the
566            // gURLs array which prevents skipped tests from showing up in the log
567            start = gThisChunk == 1 ? 0 : gURLs.indexOf(tURLs[start]);
568            end = gThisChunk == gTotalChunks ? gURLs.length : gURLs.indexOf(tURLs[end + 1]) - 1;
569            gURLs = gURLs.slice(start, end);
570
571            logger.info("Running chunk " + gThisChunk + " out of " + gTotalChunks + " chunks.  " +
572                "tests " + (start+1) + "-" + end + "/" + gURLs.length);
573        }
574
575        if (gShuffle) {
576            Shuffle(gURLs);
577        }
578
579        gTotalTests = gURLs.length;
580
581        if (!gTotalTests)
582            throw "No tests to run";
583
584        gURICanvases = {};
585        StartCurrentTest();
586    } catch (ex) {
587        //gBrowser.loadURI('data:text/plain,' + ex);
588        ++gTestResults.Exception;
589        logger.error("EXCEPTION: " + ex);
590        DoneTests();
591    }
592}
593
594function OnRefTestUnload()
595{
596  let plugin1 = getTestPlugin("Test Plug-in");
597  let plugin2 = getTestPlugin("Second Test Plug-in");
598  if (plugin1 && plugin2) {
599    plugin1.enabledState = gTestPluginEnabledStates[0];
600    plugin2.enabledState = gTestPluginEnabledStates[1];
601  } else {
602    logger.warning("Failed to get test plugin tags.");
603  }
604}
605
606// Read all available data from an input stream and return it
607// as a string.
608function getStreamContent(inputStream)
609{
610    var streamBuf = "";
611    var sis = CC["@mozilla.org/scriptableinputstream;1"].
612                  createInstance(CI.nsIScriptableInputStream);
613    sis.init(inputStream);
614
615    var available;
616    while ((available = sis.available()) != 0) {
617        streamBuf += sis.read(available);
618    }
619
620    return streamBuf;
621}
622
623// Build the sandbox for fails-if(), etc., condition evaluation.
624function BuildConditionSandbox(aURL) {
625    var sandbox = new Components.utils.Sandbox(aURL.spec);
626    var xr = CC[NS_XREAPPINFO_CONTRACTID].getService(CI.nsIXULRuntime);
627    var appInfo = CC[NS_XREAPPINFO_CONTRACTID].getService(CI.nsIXULAppInfo);
628    sandbox.isDebugBuild = gDebug.isDebugBuild;
629
630    // xr.XPCOMABI throws exception for configurations without full ABI
631    // support (mobile builds on ARM)
632    var XPCOMABI = "";
633    try {
634        XPCOMABI = xr.XPCOMABI;
635    } catch(e) {}
636
637    sandbox.xulRuntime = CU.cloneInto({widgetToolkit: xr.widgetToolkit, OS: xr.OS, XPCOMABI: XPCOMABI}, sandbox);
638
639    var testRect = gBrowser.getBoundingClientRect();
640    sandbox.smallScreen = false;
641    if (gContainingWindow.innerWidth < 800 || gContainingWindow.innerHeight < 1000) {
642        sandbox.smallScreen = true;
643    }
644
645    var gfxInfo = (NS_GFXINFO_CONTRACTID in CC) && CC[NS_GFXINFO_CONTRACTID].getService(CI.nsIGfxInfo);
646    let readGfxInfo = function (obj, key) {
647      if (gContentGfxInfo && (key in gContentGfxInfo)) {
648        return gContentGfxInfo[key];
649      }
650      return obj[key];
651    }
652
653    try {
654      sandbox.d2d = readGfxInfo(gfxInfo, "D2DEnabled");
655      sandbox.dwrite = readGfxInfo(gfxInfo, "DWriteEnabled");
656    } catch (e) {
657      sandbox.d2d = false;
658      sandbox.dwrite = false;
659    }
660
661    var info = gfxInfo.getInfo();
662    var canvasBackend = readGfxInfo(info, "AzureCanvasBackend");
663    var contentBackend = readGfxInfo(info, "AzureContentBackend");
664    var canvasAccelerated = readGfxInfo(info, "AzureCanvasAccelerated");
665
666    sandbox.azureCairo = canvasBackend == "cairo";
667    sandbox.azureQuartz = canvasBackend == "quartz";
668    sandbox.azureSkia = canvasBackend == "skia";
669    sandbox.skiaContent = contentBackend == "skia";
670    sandbox.azureSkiaGL = canvasAccelerated; // FIXME: assumes GL right now
671    // true if we are using the same Azure backend for rendering canvas and content
672    sandbox.contentSameGfxBackendAsCanvas = contentBackend == canvasBackend
673                                            || (contentBackend == "none" && canvasBackend == "cairo");
674
675    sandbox.layersGPUAccelerated =
676      gWindowUtils.layerManagerType != "Basic";
677    sandbox.d3d11 =
678      gWindowUtils.layerManagerType == "Direct3D 11";
679    sandbox.d3d9 =
680      gWindowUtils.layerManagerType == "Direct3D 9";
681    sandbox.layersOpenGL =
682      gWindowUtils.layerManagerType == "OpenGL";
683    sandbox.layersOMTC =
684      gWindowUtils.layerManagerRemote == true;
685
686    // Shortcuts for widget toolkits.
687    sandbox.B2G = xr.widgetToolkit == "gonk";
688    sandbox.Android = xr.OS == "Android" && !sandbox.B2G;
689    sandbox.cocoaWidget = xr.widgetToolkit == "cocoa";
690    sandbox.gtkWidget = xr.widgetToolkit == "gtk2"
691                        || xr.widgetToolkit == "gtk3";
692    sandbox.qtWidget = xr.widgetToolkit == "qt";
693    sandbox.winWidget = xr.widgetToolkit == "windows";
694
695    // Scrollbars that are semi-transparent. See bug 1169666.
696    sandbox.transparentScrollbars = xr.widgetToolkit == "gtk3";
697
698    if (sandbox.Android) {
699        var sysInfo = CC["@mozilla.org/system-info;1"].getService(CI.nsIPropertyBag2);
700
701        // This is currently used to distinguish Android 4.0.3 (SDK version 15)
702        // and later from Android 2.x
703        sandbox.AndroidVersion = sysInfo.getPropertyAsInt32("version");
704    }
705
706#if MOZ_ASAN
707    sandbox.AddressSanitizer = true;
708#else
709    sandbox.AddressSanitizer = false;
710#endif
711
712#if MOZ_WEBRTC
713    sandbox.webrtc = true;
714#else
715    sandbox.webrtc = false;
716#endif
717
718    var hh = CC[NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX + "http"].
719                 getService(CI.nsIHttpProtocolHandler);
720    var httpProps = ["userAgent", "appName", "appVersion", "vendor",
721                     "vendorSub", "product", "productSub", "platform",
722                     "oscpu", "language", "misc"];
723    sandbox.http = new sandbox.Object();
724    httpProps.forEach((x) => sandbox.http[x] = hh[x]);
725
726    // Set OSX to be the Mac OS X version, as an integer, or undefined
727    // for other platforms.  The integer is formed by 100 times the
728    // major version plus the minor version, so 1006 for 10.6, 1010 for
729    // 10.10, etc.
730    var osxmatch = /Mac OS X (\d+).(\d+)$/.exec(hh.oscpu);
731    sandbox.OSX = osxmatch ? parseInt(osxmatch[1]) * 100 + parseInt(osxmatch[2]) : undefined;
732
733    // see if we have the test plugin available,
734    // and set a sandox prop accordingly
735    var navigator = gContainingWindow.navigator;
736    var testPlugin = navigator.plugins["Test Plug-in"];
737    sandbox.haveTestPlugin = !!testPlugin;
738
739    // Set a flag on sandbox if the windows default theme is active
740    sandbox.windowsDefaultTheme = gContainingWindow.matchMedia("(-moz-windows-default-theme)").matches;
741
742    var prefs = CC["@mozilla.org/preferences-service;1"].
743                getService(CI.nsIPrefBranch);
744    try {
745        sandbox.nativeThemePref = !prefs.getBoolPref("mozilla.widget.disable-native-theme");
746    } catch (e) {
747        sandbox.nativeThemePref = true;
748    }
749
750    sandbox.prefs = CU.cloneInto({
751        getBoolPref: function(p) { return prefs.getBoolPref(p); },
752        getIntPref:  function(p) { return prefs.getIntPref(p); }
753    }, sandbox, { cloneFunctions: true });
754
755    // Tests shouldn't care about this except for when they need to
756    // crash the content process
757    sandbox.browserIsRemote = gBrowserIsRemote;
758    sandbox.Mulet = gB2GisMulet;
759
760    try {
761        sandbox.asyncPan = gContainingWindow.document.docShell.asyncPanZoomEnabled;
762    } catch (e) {
763        sandbox.asyncPan = false;
764    }
765
766    if (!gDumpedConditionSandbox) {
767        logger.info("Dumping JSON representation of sandbox");
768        logger.info(JSON.stringify(CU.waiveXrays(sandbox)));
769        gDumpedConditionSandbox = true;
770    }
771
772    // Graphics features
773    sandbox.usesRepeatResampling = sandbox.d2d;
774    return sandbox;
775}
776
777function AddPrefSettings(aWhere, aPrefName, aPrefValExpression, aSandbox, aTestPrefSettings, aRefPrefSettings)
778{
779    var prefVal = Components.utils.evalInSandbox("(" + aPrefValExpression + ")", aSandbox);
780    var prefType;
781    var valType = typeof(prefVal);
782    if (valType == "boolean") {
783        prefType = PREF_BOOLEAN;
784    } else if (valType == "string") {
785        prefType = PREF_STRING;
786    } else if (valType == "number" && (parseInt(prefVal) == prefVal)) {
787        prefType = PREF_INTEGER;
788    } else {
789        return false;
790    }
791    var setting = { name: aPrefName,
792                    type: prefType,
793                    value: prefVal };
794    if (aWhere != "ref-") {
795        aTestPrefSettings.push(setting);
796    }
797    if (aWhere != "test-") {
798        aRefPrefSettings.push(setting);
799    }
800    return true;
801}
802
803function ReadTopManifest(aFileURL, aFilter)
804{
805    var url = gIOService.newURI(aFileURL, null, null);
806    if (!url)
807        throw "Expected a file or http URL for the manifest.";
808    ReadManifest(url, EXPECTED_PASS, aFilter);
809}
810
811function AddTestItem(aTest, aFilter)
812{
813    if (!aFilter)
814        aFilter = [null, [], false];
815
816    globalFilter = aFilter[0];
817    manifestFilter = aFilter[1];
818    invertManifest = aFilter[2];
819    if ((globalFilter && !globalFilter.test(aTest.url1.spec)) ||
820        (manifestFilter &&
821         !(invertManifest ^ manifestFilter.test(aTest.url1.spec))))
822        return;
823    if (gFocusFilterMode == FOCUS_FILTER_NEEDS_FOCUS_TESTS &&
824        !aTest.needsFocus)
825        return;
826    if (gFocusFilterMode == FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS &&
827        aTest.needsFocus)
828        return;
829
830    if (aTest.url2 !== null)
831        aTest.identifier = [aTest.prettyPath, aTest.type, aTest.url2.spec];
832    else
833        aTest.identifier = aTest.prettyPath;
834
835    gURLs.push(aTest);
836}
837
838// Note: If you materially change the reftest manifest parsing,
839// please keep the parser in print-manifest-dirs.py in sync.
840function ReadManifest(aURL, inherited_status, aFilter)
841{
842    // Ensure each manifest is only read once. This assumes that manifests that are
843    // included with an unusual inherited_status or filters will be read via their
844    // include before they are read directly in the case of a duplicate
845    if (gManifestsLoaded.hasOwnProperty(aURL.spec)) {
846        if (gManifestsLoaded[aURL.spec] === null)
847            return;
848        else
849            aFilter = [aFilter[0], aFilter[1], true];
850    }
851    gManifestsLoaded[aURL.spec] = aFilter[1];
852
853    var secMan = CC[NS_SCRIPTSECURITYMANAGER_CONTRACTID]
854                     .getService(CI.nsIScriptSecurityManager);
855
856    var listURL = aURL;
857    var channel = NetUtil.newChannel({uri: aURL, loadUsingSystemPrincipal: true});
858    var inputStream = channel.open2();
859    if (channel instanceof Components.interfaces.nsIHttpChannel
860        && channel.responseStatus != 200) {
861      logger.error("HTTP ERROR : " + channel.responseStatus);
862    }
863    var streamBuf = getStreamContent(inputStream);
864    inputStream.close();
865    var lines = streamBuf.split(/\n|\r|\r\n/);
866
867    // Build the sandbox for fails-if(), etc., condition evaluation.
868    var sandbox = BuildConditionSandbox(aURL);
869    var lineNo = 0;
870    var urlprefix = "";
871    var defaultTestPrefSettings = [], defaultRefPrefSettings = [];
872    for (var str of lines) {
873        ++lineNo;
874        if (str.charAt(0) == "#")
875            continue; // entire line was a comment
876        var i = str.search(/\s+#/);
877        if (i >= 0)
878            str = str.substring(0, i);
879        // strip leading and trailing whitespace
880        str = str.replace(/^\s*/, '').replace(/\s*$/, '');
881        if (!str || str == "")
882            continue;
883        var items = str.split(/\s+/); // split on whitespace
884
885        if (items[0] == "url-prefix") {
886            if (items.length != 2)
887                throw "url-prefix requires one url in manifest file " + aURL.spec + " line " + lineNo;
888            urlprefix = items[1];
889            continue;
890        }
891
892        if (items[0] == "default-preferences") {
893            var m;
894            var item;
895            defaultTestPrefSettings = [];
896            defaultRefPrefSettings = [];
897            items.shift();
898            while ((item = items.shift())) {
899                if (!(m = item.match(gPrefItemRE))) {
900                    throw "Unexpected item in default-preferences list in manifest file " + aURL.spec + " line " + lineNo;
901                }
902                if (!AddPrefSettings(m[1], m[2], m[3], sandbox, defaultTestPrefSettings, defaultRefPrefSettings)) {
903                    throw "Error in pref value in manifest file " + aURL.spec + " line " + lineNo;
904                }
905            }
906            continue;
907        }
908
909        var expected_status = EXPECTED_PASS;
910        var allow_silent_fail = false;
911        var minAsserts = 0;
912        var maxAsserts = 0;
913        var needs_focus = false;
914        var slow = false;
915        var testPrefSettings = defaultTestPrefSettings.concat();
916        var refPrefSettings = defaultRefPrefSettings.concat();
917        var fuzzy_max_delta = 2;
918        var fuzzy_max_pixels = 1;
919        var chaosMode = false;
920
921        while (items[0].match(/^(fails|needs-focus|random|skip|asserts|slow|require-or|silentfail|pref|test-pref|ref-pref|fuzzy|chaos-mode)/)) {
922            var item = items.shift();
923            var stat;
924            var cond;
925            var m = item.match(/^(fails|random|skip|silentfail)-if(\(.*\))$/);
926            if (m) {
927                stat = m[1];
928                // Note: m[2] contains the parentheses, and we want them.
929                cond = Components.utils.evalInSandbox(m[2], sandbox);
930            } else if (item.match(/^(fails|random|skip)$/)) {
931                stat = item;
932                cond = true;
933            } else if (item == "needs-focus") {
934                needs_focus = true;
935                cond = false;
936            } else if ((m = item.match(/^asserts\((\d+)(-\d+)?\)$/))) {
937                cond = false;
938                minAsserts = Number(m[1]);
939                maxAsserts = (m[2] == undefined) ? minAsserts
940                                                 : Number(m[2].substring(1));
941            } else if ((m = item.match(/^asserts-if\((.*?),(\d+)(-\d+)?\)$/))) {
942                cond = false;
943                if (Components.utils.evalInSandbox("(" + m[1] + ")", sandbox)) {
944                    minAsserts = Number(m[2]);
945                    maxAsserts =
946                      (m[3] == undefined) ? minAsserts
947                                          : Number(m[3].substring(1));
948                }
949            } else if (item == "slow") {
950                cond = false;
951                slow = true;
952            } else if ((m = item.match(/^require-or\((.*?)\)$/))) {
953                var args = m[1].split(/,/);
954                if (args.length != 2) {
955                    throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": wrong number of args to require-or";
956                }
957                var [precondition_str, fallback_action] = args;
958                var preconditions = precondition_str.split(/&&/);
959                cond = false;
960                for (var precondition of preconditions) {
961                    if (precondition === "debugMode") {
962                        // Currently unimplemented. Requires asynchronous
963                        // JSD call + getting an event while no JS is running
964                        stat = fallback_action;
965                        cond = true;
966                        break;
967                    } else if (precondition === "true") {
968                        // For testing
969                    } else {
970                        // Unknown precondition. Assume it is unimplemented.
971                        stat = fallback_action;
972                        cond = true;
973                        break;
974                    }
975                }
976            } else if ((m = item.match(/^slow-if\((.*?)\)$/))) {
977                cond = false;
978                if (Components.utils.evalInSandbox("(" + m[1] + ")", sandbox))
979                    slow = true;
980            } else if (item == "silentfail") {
981                cond = false;
982                allow_silent_fail = true;
983            } else if ((m = item.match(gPrefItemRE))) {
984                cond = false;
985                if (!AddPrefSettings(m[1], m[2], m[3], sandbox, testPrefSettings, refPrefSettings)) {
986                    throw "Error in pref value in manifest file " + aURL.spec + " line " + lineNo;
987                }
988            } else if ((m = item.match(/^fuzzy\((\d+),(\d+)\)$/))) {
989              cond = false;
990              expected_status = EXPECTED_FUZZY;
991              fuzzy_max_delta = Number(m[1]);
992              fuzzy_max_pixels = Number(m[2]);
993            } else if ((m = item.match(/^fuzzy-if\((.*?),(\d+),(\d+)\)$/))) {
994              cond = false;
995              if (Components.utils.evalInSandbox("(" + m[1] + ")", sandbox)) {
996                expected_status = EXPECTED_FUZZY;
997                fuzzy_max_delta = Number(m[2]);
998                fuzzy_max_pixels = Number(m[3]);
999              }
1000            } else if (item == "chaos-mode") {
1001                cond = false;
1002                chaosMode = true;
1003            } else {
1004                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": unexpected item " + item;
1005            }
1006
1007            if (cond) {
1008                if (stat == "fails") {
1009                    expected_status = EXPECTED_FAIL;
1010                } else if (stat == "random") {
1011                    expected_status = EXPECTED_RANDOM;
1012                } else if (stat == "skip") {
1013                    expected_status = EXPECTED_DEATH;
1014                } else if (stat == "silentfail") {
1015                    allow_silent_fail = true;
1016                }
1017            }
1018        }
1019
1020        expected_status = Math.max(expected_status, inherited_status);
1021
1022        if (minAsserts > maxAsserts) {
1023            throw "Bad range in manifest file " + aURL.spec + " line " + lineNo;
1024        }
1025
1026        var runHttp = false;
1027        var httpDepth;
1028        if (items[0] == "HTTP") {
1029            runHttp = (aURL.scheme == "file"); // We can't yet run the local HTTP server
1030                                               // for non-local reftests.
1031            httpDepth = 0;
1032            items.shift();
1033        } else if (items[0].match(/HTTP\(\.\.(\/\.\.)*\)/)) {
1034            // Accept HTTP(..), HTTP(../..), HTTP(../../..), etc.
1035            runHttp = (aURL.scheme == "file"); // We can't yet run the local HTTP server
1036                                               // for non-local reftests.
1037            httpDepth = (items[0].length - 5) / 3;
1038            items.shift();
1039        }
1040
1041        // do not prefix the url for include commands or urls specifying
1042        // a protocol
1043        if (urlprefix && items[0] != "include") {
1044            if (items.length > 1 && !items[1].match(gProtocolRE)) {
1045                items[1] = urlprefix + items[1];
1046            }
1047            if (items.length > 2 && !items[2].match(gProtocolRE)) {
1048                items[2] = urlprefix + items[2];
1049            }
1050        }
1051
1052        var principal = secMan.createCodebasePrincipal(aURL, {});
1053
1054        if (items[0] == "include") {
1055            if (items.length != 2)
1056                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to include";
1057            if (runHttp)
1058                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": use of include with http";
1059            var incURI = gIOService.newURI(items[1], null, listURL);
1060            secMan.checkLoadURIWithPrincipal(principal, incURI,
1061                                             CI.nsIScriptSecurityManager.DISALLOW_SCRIPT);
1062            ReadManifest(incURI, expected_status, aFilter);
1063        } else if (items[0] == TYPE_LOAD) {
1064            if (items.length != 2)
1065                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to load";
1066            if (expected_status != EXPECTED_PASS &&
1067                expected_status != EXPECTED_DEATH)
1068                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect known failure type for load test";
1069            var [testURI] = runHttp
1070                            ? ServeFiles(principal, httpDepth,
1071                                         listURL, [items[1]])
1072                            : [gIOService.newURI(items[1], null, listURL)];
1073            var prettyPath = runHttp
1074                           ? gIOService.newURI(items[1], null, listURL).spec
1075                           : testURI.spec;
1076            secMan.checkLoadURIWithPrincipal(principal, testURI,
1077                                             CI.nsIScriptSecurityManager.DISALLOW_SCRIPT);
1078            AddTestItem({ type: TYPE_LOAD,
1079                          expected: expected_status,
1080                          allowSilentFail: allow_silent_fail,
1081                          prettyPath: prettyPath,
1082                          minAsserts: minAsserts,
1083                          maxAsserts: maxAsserts,
1084                          needsFocus: needs_focus,
1085                          slow: slow,
1086                          prefSettings1: testPrefSettings,
1087                          prefSettings2: refPrefSettings,
1088                          fuzzyMaxDelta: fuzzy_max_delta,
1089                          fuzzyMaxPixels: fuzzy_max_pixels,
1090                          url1: testURI,
1091                          url2: null,
1092                          chaosMode: chaosMode }, aFilter);
1093        } else if (items[0] == TYPE_SCRIPT) {
1094            if (items.length != 2)
1095                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to script";
1096            var [testURI] = runHttp
1097                            ? ServeFiles(principal, httpDepth,
1098                                         listURL, [items[1]])
1099                            : [gIOService.newURI(items[1], null, listURL)];
1100            var prettyPath = runHttp
1101                           ? gIOService.newURI(items[1], null, listURL).spec
1102                           : testURI.spec;
1103            secMan.checkLoadURIWithPrincipal(principal, testURI,
1104                                             CI.nsIScriptSecurityManager.DISALLOW_SCRIPT);
1105            AddTestItem({ type: TYPE_SCRIPT,
1106                          expected: expected_status,
1107                          allowSilentFail: allow_silent_fail,
1108                          prettyPath: prettyPath,
1109                          minAsserts: minAsserts,
1110                          maxAsserts: maxAsserts,
1111                          needsFocus: needs_focus,
1112                          slow: slow,
1113                          prefSettings1: testPrefSettings,
1114                          prefSettings2: refPrefSettings,
1115                          fuzzyMaxDelta: fuzzy_max_delta,
1116                          fuzzyMaxPixels: fuzzy_max_pixels,
1117                          url1: testURI,
1118                          url2: null,
1119                          chaosMode: chaosMode }, aFilter);
1120        } else if (items[0] == TYPE_REFTEST_EQUAL || items[0] == TYPE_REFTEST_NOTEQUAL) {
1121            if (items.length != 3)
1122                throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to " + items[0];
1123            var [testURI, refURI] = runHttp
1124                                  ? ServeFiles(principal, httpDepth,
1125                                               listURL, [items[1], items[2]])
1126                                  : [gIOService.newURI(items[1], null, listURL),
1127                                     gIOService.newURI(items[2], null, listURL)];
1128            var prettyPath = runHttp
1129                           ? gIOService.newURI(items[1], null, listURL).spec
1130                           : testURI.spec;
1131            secMan.checkLoadURIWithPrincipal(principal, testURI,
1132                                             CI.nsIScriptSecurityManager.DISALLOW_SCRIPT);
1133            secMan.checkLoadURIWithPrincipal(principal, refURI,
1134                                             CI.nsIScriptSecurityManager.DISALLOW_SCRIPT);
1135            AddTestItem({ type: items[0],
1136                          expected: expected_status,
1137                          allowSilentFail: allow_silent_fail,
1138                          prettyPath: prettyPath,
1139                          minAsserts: minAsserts,
1140                          maxAsserts: maxAsserts,
1141                          needsFocus: needs_focus,
1142                          slow: slow,
1143                          prefSettings1: testPrefSettings,
1144                          prefSettings2: refPrefSettings,
1145                          fuzzyMaxDelta: fuzzy_max_delta,
1146                          fuzzyMaxPixels: fuzzy_max_pixels,
1147                          url1: testURI,
1148                          url2: refURI,
1149                          chaosMode: chaosMode }, aFilter);
1150        } else {
1151            throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": unknown test type " + items[0];
1152        }
1153    }
1154}
1155
1156function AddURIUseCount(uri)
1157{
1158    if (uri == null)
1159        return;
1160
1161    var spec = uri.spec;
1162    if (spec in gURIUseCounts) {
1163        gURIUseCounts[spec]++;
1164    } else {
1165        gURIUseCounts[spec] = 1;
1166    }
1167}
1168
1169function BuildUseCounts()
1170{
1171    if (gNoCanvasCache) {
1172        return;
1173    }
1174
1175    gURIUseCounts = {};
1176    for (var i = 0; i < gURLs.length; ++i) {
1177        var url = gURLs[i];
1178        if (url.expected != EXPECTED_DEATH &&
1179            (url.type == TYPE_REFTEST_EQUAL ||
1180             url.type == TYPE_REFTEST_NOTEQUAL)) {
1181            if (url.prefSettings1.length == 0) {
1182                AddURIUseCount(gURLs[i].url1);
1183            }
1184            if (url.prefSettings2.length == 0) {
1185                AddURIUseCount(gURLs[i].url2);
1186            }
1187        }
1188    }
1189}
1190
1191function ServeFiles(manifestPrincipal, depth, aURL, files)
1192{
1193    var listURL = aURL.QueryInterface(CI.nsIFileURL);
1194    var directory = listURL.file.parent;
1195
1196    // Allow serving a tree that's an ancestor of the directory containing
1197    // the files so that they can use resources in ../ (etc.).
1198    var dirPath = "/";
1199    while (depth > 0) {
1200        dirPath = "/" + directory.leafName + dirPath;
1201        directory = directory.parent;
1202        --depth;
1203    }
1204
1205    gCount++;
1206    var path = "/" + Date.now() + "/" + gCount;
1207    gServer.registerDirectory(path + "/", directory);
1208
1209    var secMan = CC[NS_SCRIPTSECURITYMANAGER_CONTRACTID]
1210                     .getService(CI.nsIScriptSecurityManager);
1211
1212    var testbase = gIOService.newURI("http://localhost:" + gHttpServerPort +
1213                                     path + dirPath, null, null);
1214
1215    // Give the testbase URI access to XUL and XBL
1216    Services.perms.add(testbase, "allowXULXBL", Services.perms.ALLOW_ACTION);
1217
1218    function FileToURI(file)
1219    {
1220        // Only serve relative URIs via the HTTP server, not absolute
1221        // ones like about:blank.
1222        var testURI = gIOService.newURI(file, null, testbase);
1223
1224        // XXX necessary?  manifestURL guaranteed to be file, others always HTTP
1225        secMan.checkLoadURIWithPrincipal(manifestPrincipal, testURI,
1226                                         CI.nsIScriptSecurityManager.DISALLOW_SCRIPT);
1227
1228        return testURI;
1229    }
1230
1231    return files.map(FileToURI);
1232}
1233
1234// Return true iff this window is focused when this function returns.
1235function Focus()
1236{
1237    var fm = CC["@mozilla.org/focus-manager;1"].getService(CI.nsIFocusManager);
1238    fm.focusedWindow = gContainingWindow;
1239#ifdef XP_MACOSX
1240    try {
1241        var dock = CC["@mozilla.org/widget/macdocksupport;1"].getService(CI.nsIMacDockSupport);
1242        dock.activateApplication(true);
1243    } catch(ex) {
1244    }
1245#endif // XP_MACOSX
1246    return true;
1247}
1248
1249function Blur()
1250{
1251    // On non-remote reftests, this will transfer focus to the dummy window
1252    // we created to hold focus for non-needs-focus tests.  Buggy tests
1253    // (ones which require focus but don't request needs-focus) will then
1254    // fail.
1255    gContainingWindow.blur();
1256}
1257
1258function StartCurrentTest()
1259{
1260    gTestLog = [];
1261
1262    // make sure we don't run tests that are expected to kill the browser
1263    while (gURLs.length > 0) {
1264        var test = gURLs[0];
1265        logger.testStart(test.identifier);
1266        if (test.expected == EXPECTED_DEATH) {
1267            ++gTestResults.Skip;
1268            logger.testEnd(test.identifier, "SKIP");
1269            gURLs.shift();
1270        } else if (test.needsFocus && !Focus()) {
1271            // FIXME: Marking this as a known fail is dangerous!  What
1272            // if it starts failing all the time?
1273            ++gTestResults.Skip;
1274            logger.testEnd(test.identifier, "SKIP", null, "(COULDN'T GET FOCUS)");
1275            gURLs.shift();
1276        } else if (test.slow && !gRunSlowTests) {
1277            ++gTestResults.Slow;
1278            logger.testEnd(test.identifier, "SKIP", null, "(SLOW)");
1279            gURLs.shift();
1280        } else {
1281            break;
1282        }
1283    }
1284
1285    if ((gURLs.length == 0 && gRepeat == 0) ||
1286        (gRunUntilFailure && HasUnexpectedResult())) {
1287        RestoreChangedPreferences();
1288        DoneTests();
1289    } else if (gURLs.length == 0 && gRepeat > 0) {
1290        // Repeat
1291        gRepeat--;
1292        StartTests();
1293    } else {
1294        if (gURLs[0].chaosMode) {
1295            gWindowUtils.enterChaosMode();
1296        }
1297        if (!gURLs[0].needsFocus) {
1298            Blur();
1299        }
1300        var currentTest = gTotalTests - gURLs.length;
1301        gContainingWindow.document.title = "reftest: " + currentTest + " / " + gTotalTests +
1302            " (" + Math.floor(100 * (currentTest / gTotalTests)) + "%)";
1303        StartCurrentURI(1);
1304    }
1305}
1306
1307function StartCurrentURI(aState)
1308{
1309    gState = aState;
1310    gCurrentURL = gURLs[0]["url" + aState].spec;
1311
1312    RestoreChangedPreferences();
1313
1314    var prefSettings = gURLs[0]["prefSettings" + aState];
1315    if (prefSettings.length > 0) {
1316        var prefs = Components.classes["@mozilla.org/preferences-service;1"].
1317                    getService(Components.interfaces.nsIPrefBranch);
1318        var badPref = undefined;
1319        try {
1320            prefSettings.forEach(function(ps) {
1321                var oldVal;
1322                if (ps.type == PREF_BOOLEAN) {
1323                    try {
1324                        oldVal = prefs.getBoolPref(ps.name);
1325                    } catch (e) {
1326                        badPref = "boolean preference '" + ps.name + "'";
1327                        throw "bad pref";
1328                    }
1329                } else if (ps.type == PREF_STRING) {
1330                    try {
1331                        oldVal = prefs.getCharPref(ps.name);
1332                    } catch (e) {
1333                        badPref = "string preference '" + ps.name + "'";
1334                        throw "bad pref";
1335                    }
1336                } else if (ps.type == PREF_INTEGER) {
1337                    try {
1338                        oldVal = prefs.getIntPref(ps.name);
1339                    } catch (e) {
1340                        badPref = "integer preference '" + ps.name + "'";
1341                        throw "bad pref";
1342                    }
1343                } else {
1344                    throw "internal error - unknown preference type";
1345                }
1346                if (oldVal != ps.value) {
1347                    gPrefsToRestore.push( { name: ps.name,
1348                                            type: ps.type,
1349                                            value: oldVal } );
1350                    var value = ps.value;
1351                    if (ps.type == PREF_BOOLEAN) {
1352                        prefs.setBoolPref(ps.name, value);
1353                    } else if (ps.type == PREF_STRING) {
1354                        prefs.setCharPref(ps.name, value);
1355                        value = '"' + value + '"';
1356                    } else if (ps.type == PREF_INTEGER) {
1357                        prefs.setIntPref(ps.name, value);
1358                    }
1359                    logger.info("SET PREFERENCE pref(" + ps.name + "," + value + ")");
1360                }
1361            });
1362        } catch (e) {
1363            if (e == "bad pref") {
1364                var test = gURLs[0];
1365                if (test.expected == EXPECTED_FAIL) {
1366                    logger.testEnd(test.identifier, "FAIL", "FAIL",
1367                                   "(SKIPPED; " + badPref + " not known or wrong type)");
1368                    ++gTestResults.Skip;
1369                } else {
1370                    logger.testEnd(test.identifier, "FAIL", "PASS",
1371                                   badPref + " not known or wrong type");
1372                    ++gTestResults.UnexpectedFail;
1373                }
1374
1375                // skip the test that had a bad preference
1376                gURLs.shift();
1377                StartCurrentTest();
1378                return;
1379            } else {
1380                throw e;
1381            }
1382        }
1383    }
1384
1385    if (prefSettings.length == 0 &&
1386        gURICanvases[gCurrentURL] &&
1387        (gURLs[0].type == TYPE_REFTEST_EQUAL ||
1388         gURLs[0].type == TYPE_REFTEST_NOTEQUAL) &&
1389        gURLs[0].maxAsserts == 0) {
1390        // Pretend the document loaded --- RecordResult will notice
1391        // there's already a canvas for this URL
1392        gContainingWindow.setTimeout(RecordResult, 0);
1393    } else {
1394        var currentTest = gTotalTests - gURLs.length;
1395        // Log this to preserve the same overall log format,
1396        // should be removed if the format is updated
1397        gDumpFn("REFTEST TEST-LOAD | " + gCurrentURL + " | " + currentTest + " / " + gTotalTests +
1398                " (" + Math.floor(100 * (currentTest / gTotalTests)) + "%)\n");
1399        TestBuffer("START " + gCurrentURL);
1400        var type = gURLs[0].type
1401        if (TYPE_SCRIPT == type) {
1402            SendLoadScriptTest(gCurrentURL, gLoadTimeout);
1403        } else {
1404            SendLoadTest(type, gCurrentURL, gLoadTimeout);
1405        }
1406    }
1407}
1408
1409function DoneTests()
1410{
1411    logger.suiteEnd(extra={'results': gTestResults});
1412    logger.info("Slowest test took " + gSlowestTestTime + "ms (" + gSlowestTestURL + ")");
1413    logger.info("Total canvas count = " + gRecycledCanvases.length);
1414    if (gFailedUseWidgetLayers) {
1415        LogWidgetLayersFailure();
1416    }
1417
1418    function onStopped() {
1419        let appStartup = CC["@mozilla.org/toolkit/app-startup;1"].getService(CI.nsIAppStartup);
1420        appStartup.quit(CI.nsIAppStartup.eForceQuit);
1421    }
1422    if (gServer) {
1423        gServer.stop(onStopped);
1424    }
1425    else {
1426        onStopped();
1427    }
1428}
1429
1430function UpdateCanvasCache(url, canvas)
1431{
1432    var spec = url.spec;
1433
1434    --gURIUseCounts[spec];
1435
1436    if (gURIUseCounts[spec] == 0) {
1437        ReleaseCanvas(canvas);
1438        delete gURICanvases[spec];
1439    } else if (gURIUseCounts[spec] > 0) {
1440        gURICanvases[spec] = canvas;
1441    } else {
1442        throw "Use counts were computed incorrectly";
1443    }
1444}
1445
1446// Recompute drawWindow flags for every drawWindow operation.
1447// We have to do this every time since our window can be
1448// asynchronously resized (e.g. by the window manager, to make
1449// it fit on screen) at unpredictable times.
1450// Fortunately this is pretty cheap.
1451function DoDrawWindow(ctx, x, y, w, h)
1452{
1453    var flags = ctx.DRAWWINDOW_DRAW_CARET | ctx.DRAWWINDOW_DRAW_VIEW;
1454    var testRect = gBrowser.getBoundingClientRect();
1455    if (gIgnoreWindowSize ||
1456        (0 <= testRect.left &&
1457         0 <= testRect.top &&
1458         gContainingWindow.innerWidth >= testRect.right &&
1459         gContainingWindow.innerHeight >= testRect.bottom)) {
1460        // We can use the window's retained layer manager
1461        // because the window is big enough to display the entire
1462        // browser element
1463        flags |= ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
1464    } else if (gBrowserIsRemote) {
1465        logger.error(gCurrentURL + " | can't drawWindow remote content");
1466        ++gTestResults.Exception;
1467    }
1468
1469    if (gDrawWindowFlags != flags) {
1470        // Every time the flags change, dump the new state.
1471        gDrawWindowFlags = flags;
1472        var flagsStr = "DRAWWINDOW_DRAW_CARET | DRAWWINDOW_DRAW_VIEW";
1473        if (flags & ctx.DRAWWINDOW_USE_WIDGET_LAYERS) {
1474            flagsStr += " | DRAWWINDOW_USE_WIDGET_LAYERS";
1475        } else {
1476            // Output a special warning because we need to be able to detect
1477            // this whenever it happens.
1478            LogWidgetLayersFailure();
1479            gFailedUseWidgetLayers = true;
1480        }
1481        logger.info("drawWindow flags = " + flagsStr +
1482                    "; window size = " + gContainingWindow.innerWidth + "," + gContainingWindow.innerHeight +
1483                    "; test browser size = " + testRect.width + "," + testRect.height);
1484    }
1485
1486    TestBuffer("DoDrawWindow " + x + "," + y + "," + w + "," + h);
1487    ctx.drawWindow(gContainingWindow, x, y, w, h, "rgb(255,255,255)",
1488                   gDrawWindowFlags);
1489}
1490
1491function InitCurrentCanvasWithSnapshot()
1492{
1493    TestBuffer("Initializing canvas snapshot");
1494
1495    if (gURLs[0].type == TYPE_LOAD || gURLs[0].type == TYPE_SCRIPT) {
1496        // We don't want to snapshot this kind of test
1497        return false;
1498    }
1499
1500    if (!gCurrentCanvas) {
1501        gCurrentCanvas = AllocateCanvas();
1502    }
1503
1504    var ctx = gCurrentCanvas.getContext("2d");
1505    DoDrawWindow(ctx, 0, 0, gCurrentCanvas.width, gCurrentCanvas.height);
1506    return true;
1507}
1508
1509function UpdateCurrentCanvasForInvalidation(rects)
1510{
1511    TestBuffer("Updating canvas for invalidation");
1512
1513    if (!gCurrentCanvas) {
1514        return;
1515    }
1516
1517    var ctx = gCurrentCanvas.getContext("2d");
1518    for (var i = 0; i < rects.length; ++i) {
1519        var r = rects[i];
1520        // Set left/top/right/bottom to pixel boundaries
1521        var left = Math.floor(r.left);
1522        var top = Math.floor(r.top);
1523        var right = Math.ceil(r.right);
1524        var bottom = Math.ceil(r.bottom);
1525
1526        // Clamp the values to the canvas size
1527        left = Math.max(0, Math.min(left, gCurrentCanvas.width));
1528        top = Math.max(0, Math.min(top, gCurrentCanvas.height));
1529        right = Math.max(0, Math.min(right, gCurrentCanvas.width));
1530        bottom = Math.max(0, Math.min(bottom, gCurrentCanvas.height));
1531
1532        ctx.save();
1533        ctx.translate(left, top);
1534        DoDrawWindow(ctx, left, top, right - left, bottom - top);
1535        ctx.restore();
1536    }
1537}
1538
1539function UpdateWholeCurrentCanvasForInvalidation()
1540{
1541    TestBuffer("Updating entire canvas for invalidation");
1542
1543    if (!gCurrentCanvas) {
1544        return;
1545    }
1546
1547    var ctx = gCurrentCanvas.getContext("2d");
1548    DoDrawWindow(ctx, 0, 0, gCurrentCanvas.width, gCurrentCanvas.height);
1549}
1550
1551function RecordResult(testRunTime, errorMsg, scriptResults)
1552{
1553    TestBuffer("RecordResult fired");
1554
1555    // Keep track of which test was slowest, and how long it took.
1556    if (testRunTime > gSlowestTestTime) {
1557        gSlowestTestTime = testRunTime;
1558        gSlowestTestURL  = gCurrentURL;
1559    }
1560
1561    // Not 'const ...' because of 'EXPECTED_*' value dependency.
1562    var outputs = {};
1563    outputs[EXPECTED_PASS] = {
1564        true:  {s: ["PASS", "PASS"], n: "Pass"},
1565        false: {s: ["FAIL", "PASS"], n: "UnexpectedFail"}
1566    };
1567    outputs[EXPECTED_FAIL] = {
1568        true:  {s: ["PASS", "FAIL"], n: "UnexpectedPass"},
1569        false: {s: ["FAIL", "FAIL"], n: "KnownFail"}
1570    };
1571    outputs[EXPECTED_RANDOM] = {
1572        true:  {s: ["PASS", "PASS"], n: "Random"},
1573        false: {s: ["FAIL", "FAIL"], n: "Random"}
1574    };
1575    outputs[EXPECTED_FUZZY] = outputs[EXPECTED_PASS];
1576
1577    var output;
1578    var extra;
1579
1580    if (gURLs[0].type == TYPE_LOAD) {
1581        ++gTestResults.LoadOnly;
1582        logger.testEnd(gURLs[0].identifier, "PASS", "PASS", "(LOAD ONLY)");
1583        gCurrentCanvas = null;
1584        FinishTestItem();
1585        return;
1586    }
1587    if (gURLs[0].type == TYPE_SCRIPT) {
1588        var expected = gURLs[0].expected;
1589
1590        if (errorMsg) {
1591            // Force an unexpected failure to alert the test author to fix the test.
1592            expected = EXPECTED_PASS;
1593        } else if (scriptResults.length == 0) {
1594             // This failure may be due to a JavaScript Engine bug causing
1595             // early termination of the test. If we do not allow silent
1596             // failure, report an error.
1597             if (!gURLs[0].allowSilentFail)
1598                 errorMsg = "No test results reported. (SCRIPT)\n";
1599             else
1600                 logger.info("An expected silent failure occurred");
1601        }
1602
1603        if (errorMsg) {
1604            output = outputs[expected][false];
1605            extra = { status_msg: output.n };
1606            ++gTestResults[output.n];
1607            logger.testEnd(gURLs[0].identifier, output.s[0], output.s[1], errorMsg, null, extra);
1608            FinishTestItem();
1609            return;
1610        }
1611
1612        var anyFailed = scriptResults.some(function(result) { return !result.passed; });
1613        var outputPair;
1614        if (anyFailed && expected == EXPECTED_FAIL) {
1615            // If we're marked as expected to fail, and some (but not all) tests
1616            // passed, treat those tests as though they were marked random
1617            // (since we can't tell whether they were really intended to be
1618            // marked failing or not).
1619            outputPair = { true: outputs[EXPECTED_RANDOM][true],
1620                           false: outputs[expected][false] };
1621        } else {
1622            outputPair = outputs[expected];
1623        }
1624        var index = 0;
1625        scriptResults.forEach(function(result) {
1626                var output = outputPair[result.passed];
1627                var extra = { status_msg: output.n };
1628
1629                ++gTestResults[output.n];
1630                logger.testEnd(gURLs[0].identifier, output.s[0], output.s[1],
1631                               result.description + " item " + (++index), null, extra);
1632            });
1633
1634        if (anyFailed && expected == EXPECTED_PASS) {
1635            FlushTestBuffer();
1636        }
1637
1638        FinishTestItem();
1639        return;
1640    }
1641
1642    if (gURLs[0]["prefSettings" + gState].length == 0 &&
1643        gURICanvases[gCurrentURL]) {
1644        gCurrentCanvas = gURICanvases[gCurrentURL];
1645    }
1646    if (gCurrentCanvas == null) {
1647        logger.error(gCurrentURL, "program error managing snapshots");
1648        ++gTestResults.Exception;
1649    }
1650    if (gState == 1) {
1651        gCanvas1 = gCurrentCanvas;
1652    } else {
1653        gCanvas2 = gCurrentCanvas;
1654    }
1655    gCurrentCanvas = null;
1656
1657    ResetRenderingState();
1658
1659    switch (gState) {
1660        case 1:
1661            // First document has been loaded.
1662            // Proceed to load the second document.
1663
1664            CleanUpCrashDumpFiles();
1665            StartCurrentURI(2);
1666            break;
1667        case 2:
1668            // Both documents have been loaded. Compare the renderings and see
1669            // if the comparison result matches the expected result specified
1670            // in the manifest.
1671
1672            // number of different pixels
1673            var differences;
1674            // whether the two renderings match:
1675            var equal;
1676            var maxDifference = {};
1677
1678            differences = gWindowUtils.compareCanvases(gCanvas1, gCanvas2, maxDifference);
1679            equal = (differences == 0);
1680
1681            // what is expected on this platform (PASS, FAIL, or RANDOM)
1682            var expected = gURLs[0].expected;
1683
1684            if (maxDifference.value > 0 && maxDifference.value <= gURLs[0].fuzzyMaxDelta &&
1685                differences <= gURLs[0].fuzzyMaxPixels) {
1686                if (equal) {
1687                    throw "Inconsistent result from compareCanvases.";
1688                }
1689                equal = expected == EXPECTED_FUZZY;
1690                logger.info("REFTEST fuzzy match");
1691            }
1692
1693            var failedExtraCheck = gFailedNoPaint || gFailedOpaqueLayer || gFailedAssignedLayer;
1694
1695            // whether the comparison result matches what is in the manifest
1696            var test_passed = (equal == (gURLs[0].type == TYPE_REFTEST_EQUAL)) && !failedExtraCheck;
1697
1698            output = outputs[expected][test_passed];
1699            extra = { status_msg: output.n };
1700
1701            ++gTestResults[output.n];
1702
1703            // It's possible that we failed both an "extra check" and the normal comparison, but we don't
1704            // have a way to annotate these separately, so just print an error for the extra check failures.
1705            if (failedExtraCheck) {
1706                var failures = [];
1707                if (gFailedNoPaint) {
1708                    failures.push("failed reftest-no-paint");
1709                }
1710                // The gFailed*Messages arrays will contain messages from both the test and the reference.
1711                if (gFailedOpaqueLayer) {
1712                    failures.push("failed reftest-opaque-layer: " + gFailedOpaqueLayerMessages.join(", "));
1713                }
1714                if (gFailedAssignedLayer) {
1715                    failures.push("failed reftest-assigned-layer: " + gFailedAssignedLayerMessages.join(", "));
1716                }
1717                var failureString = failures.join(", ");
1718                logger.testEnd(gURLs[0].identifier, output.s[0], output.s[1], failureString, null, extra);
1719            } else {
1720                var message = "image comparison";
1721                if (!test_passed && expected == EXPECTED_PASS ||
1722                    !test_passed && expected == EXPECTED_FUZZY ||
1723                    test_passed && expected == EXPECTED_FAIL) {
1724                    if (!equal) {
1725                        extra.max_difference = maxDifference.value;
1726                        extra.differences = differences;
1727                        var image1 = gCanvas1.toDataURL();
1728                        var image2 = gCanvas2.toDataURL();
1729                        extra.reftest_screenshots = [
1730                            {url:gURLs[0].identifier[0],
1731                             screenshot: image1.slice(image1.indexOf(",") + 1)},
1732                            gURLs[0].identifier[1],
1733                            {url:gURLs[0].identifier[2],
1734                             screenshot: image2.slice(image2.indexOf(",") + 1)}
1735                        ];
1736                        extra.image1 = image1;
1737                        extra.image2 = image2;
1738                        message += (", max difference: " + extra.max_difference +
1739                                    ", number of differing pixels: " + differences);
1740                    } else {
1741                        extra.image1 = gCanvas1.toDataURL();
1742                    }
1743                }
1744                logger.testEnd(gURLs[0].identifier, output.s[0], output.s[1], message, null, extra);
1745
1746                if (gNoCanvasCache) {
1747                    ReleaseCanvas(gCanvas1);
1748                    ReleaseCanvas(gCanvas2);
1749                } else {
1750                    if (gURLs[0].prefSettings1.length == 0) {
1751                        UpdateCanvasCache(gURLs[0].url1, gCanvas1);
1752                    }
1753                    if (gURLs[0].prefSettings2.length == 0) {
1754                        UpdateCanvasCache(gURLs[0].url2, gCanvas2);
1755                    }
1756                }
1757            }
1758
1759            if ((!test_passed && expected == EXPECTED_PASS) || (test_passed && expected == EXPECTED_FAIL)) {
1760                FlushTestBuffer();
1761            }
1762
1763            CleanUpCrashDumpFiles();
1764            FinishTestItem();
1765            break;
1766        default:
1767            throw "Unexpected state.";
1768    }
1769}
1770
1771function LoadFailed(why)
1772{
1773    ++gTestResults.FailedLoad;
1774    // Once bug 896840 is fixed, this can go away, but for now it will give log
1775    // output that is TBPL starable for bug 789751 and bug 720452.
1776    if (!why) {
1777        logger.error("load failed with unknown reason");
1778    }
1779    logger.testEnd(gURLs[0]["url" + gState].spec, "FAIL", "PASS", "load failed: " + why);
1780    FlushTestBuffer();
1781    FinishTestItem();
1782}
1783
1784function RemoveExpectedCrashDumpFiles()
1785{
1786    if (gExpectingProcessCrash) {
1787        for (let crashFilename of gExpectedCrashDumpFiles) {
1788            let file = gCrashDumpDir.clone();
1789            file.append(crashFilename);
1790            if (file.exists()) {
1791                file.remove(false);
1792            }
1793        }
1794    }
1795    gExpectedCrashDumpFiles.length = 0;
1796}
1797
1798function FindUnexpectedCrashDumpFiles()
1799{
1800    if (!gCrashDumpDir.exists()) {
1801        return;
1802    }
1803
1804    let entries = gCrashDumpDir.directoryEntries;
1805    if (!entries) {
1806        return;
1807    }
1808
1809    let foundCrashDumpFile = false;
1810    while (entries.hasMoreElements()) {
1811        let file = entries.getNext().QueryInterface(CI.nsIFile);
1812        let path = String(file.path);
1813        if (path.match(/\.(dmp|extra)$/) && !gUnexpectedCrashDumpFiles[path]) {
1814            if (!foundCrashDumpFile) {
1815                ++gTestResults.UnexpectedFail;
1816                foundCrashDumpFile = true;
1817                logger.testEnd(gCurrentURL, "FAIL", "PASS", "This test left crash dumps behind, but we weren't expecting it to!");
1818            }
1819            logger.info("Found unexpected crash dump file " + path);
1820            gUnexpectedCrashDumpFiles[path] = true;
1821        }
1822    }
1823}
1824
1825function CleanUpCrashDumpFiles()
1826{
1827    RemoveExpectedCrashDumpFiles();
1828    FindUnexpectedCrashDumpFiles();
1829    gExpectingProcessCrash = false;
1830}
1831
1832function FinishTestItem()
1833{
1834    // Replace document with BLANK_URL_FOR_CLEARING in case there are
1835    // assertions when unloading.
1836    logger.debug("Loading a blank page");
1837    // After clearing, content will notify us of the assertion count
1838    // and tests will continue.
1839    SendClear();
1840    gFailedNoPaint = false;
1841    gFailedOpaqueLayer = false;
1842    gFailedOpaqueLayerMessages = [];
1843    gFailedAssignedLayer = false;
1844    gFailedAssignedLayerMessages = [];
1845}
1846
1847function DoAssertionCheck(numAsserts)
1848{
1849    if (gDebug.isDebugBuild) {
1850        if (gBrowserIsRemote) {
1851            // Count chrome-process asserts too when content is out of
1852            // process.
1853            var newAssertionCount = gDebug.assertionCount;
1854            var numLocalAsserts = newAssertionCount - gAssertionCount;
1855            gAssertionCount = newAssertionCount;
1856
1857            numAsserts += numLocalAsserts;
1858        }
1859
1860        var minAsserts = gURLs[0].minAsserts;
1861        var maxAsserts = gURLs[0].maxAsserts;
1862
1863        var expectedAssertions = "expected " + minAsserts;
1864        if (minAsserts != maxAsserts) {
1865            expectedAssertions += " to " + maxAsserts;
1866        }
1867        expectedAssertions += " assertions";
1868
1869        if (numAsserts < minAsserts) {
1870            ++gTestResults.AssertionUnexpectedFixed;
1871            gDumpFn("REFTEST TEST-UNEXPECTED-PASS | " + gURLs[0].prettyPath +
1872                    " | assertion count " + numAsserts + " is less than " +
1873                       expectedAssertions + "\n");
1874        } else if (numAsserts > maxAsserts) {
1875            ++gTestResults.AssertionUnexpected;
1876            gDumpFn("REFTEST TEST-UNEXPECTED-FAIL | " + gURLs[0].prettyPath +
1877                    " | assertion count " + numAsserts + " is more than " +
1878                       expectedAssertions + "\n");
1879        } else if (numAsserts != 0) {
1880            ++gTestResults.AssertionKnown;
1881            gDumpFn("REFTEST TEST-KNOWN-FAIL | " + gURLs[0].prettyPath +
1882                    "assertion count " + numAsserts + " matches " +
1883                    expectedAssertions + "\n");
1884        }
1885    }
1886
1887    if (gURLs[0].chaosMode) {
1888        gWindowUtils.leaveChaosMode();
1889    }
1890
1891    // And start the next test.
1892    gURLs.shift();
1893    StartCurrentTest();
1894}
1895
1896function ResetRenderingState()
1897{
1898    SendResetRenderingState();
1899    // We would want to clear any viewconfig here, if we add support for it
1900}
1901
1902function RestoreChangedPreferences()
1903{
1904    if (gPrefsToRestore.length > 0) {
1905        var prefs = Components.classes["@mozilla.org/preferences-service;1"].
1906                    getService(Components.interfaces.nsIPrefBranch);
1907        gPrefsToRestore.reverse();
1908        gPrefsToRestore.forEach(function(ps) {
1909            var value = ps.value;
1910            if (ps.type == PREF_BOOLEAN) {
1911                prefs.setBoolPref(ps.name, value);
1912            } else if (ps.type == PREF_STRING) {
1913                prefs.setCharPref(ps.name, value);
1914                value = '"' + value + '"';
1915            } else if (ps.type == PREF_INTEGER) {
1916                prefs.setIntPref(ps.name, value);
1917            }
1918            logger.info("RESTORE PREFERENCE pref(" + ps.name + "," + value + ")");
1919        });
1920        gPrefsToRestore = [];
1921    }
1922}
1923
1924function RegisterMessageListenersAndLoadContentScript()
1925{
1926    gBrowserMessageManager.addMessageListener(
1927        "reftest:AssertionCount",
1928        function (m) { RecvAssertionCount(m.json.count); }
1929    );
1930    gBrowserMessageManager.addMessageListener(
1931        "reftest:ContentReady",
1932        function (m) { return RecvContentReady(m.data); }
1933    );
1934    gBrowserMessageManager.addMessageListener(
1935        "reftest:Exception",
1936        function (m) { RecvException(m.json.what) }
1937    );
1938    gBrowserMessageManager.addMessageListener(
1939        "reftest:FailedLoad",
1940        function (m) { RecvFailedLoad(m.json.why); }
1941    );
1942    gBrowserMessageManager.addMessageListener(
1943        "reftest:FailedNoPaint",
1944        function (m) { RecvFailedNoPaint(); }
1945    );
1946    gBrowserMessageManager.addMessageListener(
1947        "reftest:FailedOpaqueLayer",
1948        function (m) { RecvFailedOpaqueLayer(m.json.why); }
1949    );
1950    gBrowserMessageManager.addMessageListener(
1951        "reftest:FailedAssignedLayer",
1952        function (m) { RecvFailedAssignedLayer(m.json.why); }
1953    );
1954    gBrowserMessageManager.addMessageListener(
1955        "reftest:InitCanvasWithSnapshot",
1956        function (m) { return RecvInitCanvasWithSnapshot(); }
1957    );
1958    gBrowserMessageManager.addMessageListener(
1959        "reftest:Log",
1960        function (m) { RecvLog(m.json.type, m.json.msg); }
1961    );
1962    gBrowserMessageManager.addMessageListener(
1963        "reftest:ScriptResults",
1964        function (m) { RecvScriptResults(m.json.runtimeMs, m.json.error, m.json.results); }
1965    );
1966    gBrowserMessageManager.addMessageListener(
1967        "reftest:TestDone",
1968        function (m) { RecvTestDone(m.json.runtimeMs); }
1969    );
1970    gBrowserMessageManager.addMessageListener(
1971        "reftest:UpdateCanvasForInvalidation",
1972        function (m) { RecvUpdateCanvasForInvalidation(m.json.rects); }
1973    );
1974    gBrowserMessageManager.addMessageListener(
1975        "reftest:UpdateWholeCanvasForInvalidation",
1976        function (m) { RecvUpdateWholeCanvasForInvalidation(); }
1977    );
1978    gBrowserMessageManager.addMessageListener(
1979        "reftest:ExpectProcessCrash",
1980        function (m) { RecvExpectProcessCrash(); }
1981    );
1982
1983    gBrowserMessageManager.loadFrameScript("chrome://reftest/content/reftest-content.js", true, true);
1984}
1985
1986function RecvAssertionCount(count)
1987{
1988    DoAssertionCheck(count);
1989}
1990
1991function RecvContentReady(info)
1992{
1993    gContentGfxInfo = info.gfx;
1994    InitAndStartRefTests();
1995    return { remote: gBrowserIsRemote };
1996}
1997
1998function RecvException(what)
1999{
2000    logger.error(gCurrentURL + " | " + what);
2001    ++gTestResults.Exception;
2002}
2003
2004function RecvFailedLoad(why)
2005{
2006    LoadFailed(why);
2007}
2008
2009function RecvFailedNoPaint()
2010{
2011    gFailedNoPaint = true;
2012}
2013
2014function RecvFailedOpaqueLayer(why) {
2015    gFailedOpaqueLayer = true;
2016    gFailedOpaqueLayerMessages.push(why);
2017}
2018
2019function RecvFailedAssignedLayer(why) {
2020    gFailedAssignedLayer = true;
2021    gFailedAssignedLayerMessages.push(why);
2022}
2023
2024function RecvInitCanvasWithSnapshot()
2025{
2026    var painted = InitCurrentCanvasWithSnapshot();
2027    return { painted: painted };
2028}
2029
2030function RecvLog(type, msg)
2031{
2032    msg = "[CONTENT] " + msg;
2033    if (type == "info") {
2034        TestBuffer(msg);
2035    } else if (type == "warning") {
2036        logger.warning(msg);
2037    } else {
2038        logger.error("REFTEST TEST-UNEXPECTED-FAIL | " + gCurrentURL + " | unknown log type " + type + "\n");
2039        ++gTestResults.Exception;
2040    }
2041}
2042
2043function RecvScriptResults(runtimeMs, error, results)
2044{
2045    RecordResult(runtimeMs, error, results);
2046}
2047
2048function RecvTestDone(runtimeMs)
2049{
2050    RecordResult(runtimeMs, '', [ ]);
2051}
2052
2053function RecvUpdateCanvasForInvalidation(rects)
2054{
2055    UpdateCurrentCanvasForInvalidation(rects);
2056}
2057
2058function RecvUpdateWholeCanvasForInvalidation()
2059{
2060    UpdateWholeCurrentCanvasForInvalidation();
2061}
2062
2063function OnProcessCrashed(subject, topic, data)
2064{
2065    var id;
2066    subject = subject.QueryInterface(CI.nsIPropertyBag2);
2067    if (topic == "plugin-crashed") {
2068        id = subject.getPropertyAsAString("pluginDumpID");
2069    } else if (topic == "ipc:content-shutdown") {
2070        id = subject.getPropertyAsAString("dumpID");
2071    }
2072    if (id) {
2073        gExpectedCrashDumpFiles.push(id + ".dmp");
2074        gExpectedCrashDumpFiles.push(id + ".extra");
2075    }
2076}
2077
2078function RegisterProcessCrashObservers()
2079{
2080    var os = CC[NS_OBSERVER_SERVICE_CONTRACTID]
2081             .getService(CI.nsIObserverService);
2082    os.addObserver(OnProcessCrashed, "plugin-crashed", false);
2083    os.addObserver(OnProcessCrashed, "ipc:content-shutdown", false);
2084}
2085
2086function RecvExpectProcessCrash()
2087{
2088    gExpectingProcessCrash = true;
2089}
2090
2091function SendClear()
2092{
2093    gBrowserMessageManager.sendAsyncMessage("reftest:Clear");
2094}
2095
2096function SendLoadScriptTest(uri, timeout)
2097{
2098    gBrowserMessageManager.sendAsyncMessage("reftest:LoadScriptTest",
2099                                            { uri: uri, timeout: timeout });
2100}
2101
2102function SendLoadTest(type, uri, timeout)
2103{
2104    gBrowserMessageManager.sendAsyncMessage("reftest:LoadTest",
2105                                            { type: type, uri: uri, timeout: timeout }
2106    );
2107}
2108
2109function SendResetRenderingState()
2110{
2111    gBrowserMessageManager.sendAsyncMessage("reftest:ResetRenderingState");
2112}
2113