reftest.jsm 66.9 KB
Newer Older
1
/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- /
2
/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
3
4
5
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
"use strict";
7

8
var EXPORTED_SYMBOLS = [
9
10
11
12
    "OnRefTestLoad",
    "OnRefTestUnload",
    "getTestPlugin"
];
13

14
Cu.import("resource://gre/modules/FileUtils.jsm");
15
16
17
18
19
Cu.import("resource://reftest/globals.jsm", this);
Cu.import("resource://reftest/httpd.jsm", this);
Cu.import("resource://reftest/manifest.jsm", this);
Cu.import("resource://reftest/StructuredLog.jsm", this);
Cu.import("resource://reftest/PerTestCoverageUtils.jsm", this);
20
21
Cu.import("resource://gre/modules/Services.jsm");
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
22

23
24
25
26
const { E10SUtils } = ChromeUtils.import(
  "resource://gre/modules/E10SUtils.jsm"
);

27
XPCOMUtils.defineLazyGetter(this, "OS", function() {
28
    const { OS } = Cu.import("resource://gre/modules/osfile.jsm");
29
30
31
    return OS;
});

32
33
function HasUnexpectedResult()
{
34
35
36
37
38
39
    return g.testResults.Exception > 0 ||
           g.testResults.FailedLoad > 0 ||
           g.testResults.UnexpectedFail > 0 ||
           g.testResults.UnexpectedPass > 0 ||
           g.testResults.AssertionUnexpected > 0 ||
           g.testResults.AssertionUnexpectedFixed > 0;
40
41
}

42
// By default we just log to stdout
43
44
var gDumpFn = function(line) {
  dump(line);
45
  if (g.logFile) {
46
    g.logFile.writeString(line);
47
48
  }
}
49
50
var gDumpRawLog = function(record) {
  // Dump JSON representation of data on a single line
51
52
53
  var line = "\n" + JSON.stringify(record) + "\n";
  dump(line);

54
  if (g.logFile) {
55
    g.logFile.writeString(line);
56
  }
57
}
58
59
g.logger = new StructuredLogger('reftest', gDumpRawLog);
var logger = g.logger;
60

61
function TestBuffer(str)
62
{
63
  logger.debug(str);
64
  g.testLog.push(str);
65
66
}

67
68
69
70
71
72
73
74
75
function isWebRenderOnAndroidDevice() {
  var xr = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
  // This is the best we can do for now; maybe in the future we'll have
  // more correct detection of this case.
  return xr.OS == "Android" &&
      g.browserIsRemote &&
      g.windowUtils.layerManagerType == "WebRender";
}

76
function FlushTestBuffer()
77
{
78
  // In debug mode, we've dumped all these messages already.
79
80
81
  if (g.logLevel !== 'debug') {
    for (var i = 0; i < g.testLog.length; ++i) {
      logger.info("Saved log: " + g.testLog[i]);
82
    }
83
  }
84
  g.testLog = [];
85
86
}

87
88
function LogWidgetLayersFailure()
{
89
90
91
92
93
94
95
96
97
98
99
100
101
  logger.error(
    "Screen resolution is too low - USE_WIDGET_LAYERS was disabled. " +
    (g.browserIsRemote ?
      "Since E10s is enabled, there is no fallback rendering path!" :
      "The fallback rendering path is not reliably consistent with on-screen rendering."));

  logger.error(
    "If you cannot increase your screen resolution you can try reducing " +
    "gecko's pixel scaling by adding something like '--setpref " +
    "layout.css.devPixelsPerPx=1.0' to your './mach reftest' command " +
    "(possibly as an alias in ~/.mozbuild/machrc). Note that this is " +
    "inconsistent with CI testing, and may interfere with HighDPI/" +
    "reftest-zoom tests.");
102
103
}

104
105
function AllocateCanvas()
{
106
107
    if (g.recycledCanvases.length > 0) {
        return g.recycledCanvases.shift();
108
    }
109

110
111
    var canvas = g.containingWindow.document.createElementNS(XHTML_NS, "canvas");
    var r = g.browser.getBoundingClientRect();
112
113
    canvas.setAttribute("width", Math.ceil(r.width));
    canvas.setAttribute("height", Math.ceil(r.height));
114

115
116
117
118
119
    return canvas;
}

function ReleaseCanvas(canvas)
{
120
    // store a maximum of 2 canvases, if we're not caching
121
122
    if (!g.noCanvasCache || g.recycledCanvases.length < 2) {
        g.recycledCanvases.push(canvas);
123
    }
124
125
}

126
127
128
129
130
131
132
133
134
function IDForEventTarget(event)
{
    try {
        return "'" + event.target.getAttribute('id') + "'";
    } catch (ex) {
        return "<unknown>";
    }
}

135
function getTestPlugin(aName) {
136
  var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
137
138
139
140
141
142
143
144
  var tags = ph.getPluginTags();

  // Find the test plugin
  for (var i = 0; i < tags.length; i++) {
    if (tags[i].name == aName)
      return tags[i];
  }

145
  logger.warning("Failed to find the test-plugin.");
146
147
148
  return null;
}

149
function OnRefTestLoad(win)
150
{
151
152
153
    g.crashDumpDir = Cc[NS_DIRECTORY_SERVICE_CONTRACTID]
                    .getService(Ci.nsIProperties)
                    .get("ProfD", Ci.nsIFile);
154
    g.crashDumpDir.append("minidumps");
155

156
157
158
    g.pendingCrashDumpDir = Cc[NS_DIRECTORY_SERVICE_CONTRACTID]
                    .getService(Ci.nsIProperties)
                    .get("UAppData", Ci.nsIFile);
159
160
    g.pendingCrashDumpDir.append("Crash Reports");
    g.pendingCrashDumpDir.append("pending");
161

162
163
    var env = Cc["@mozilla.org/process/environment;1"].
              getService(Ci.nsIEnvironment);
164

165
166
    var prefs = Cc["@mozilla.org/preferences-service;1"].
                getService(Ci.nsIPrefBranch);
167
    g.browserIsRemote = prefs.getBoolPref("browser.tabs.remote.autostart", false);
168
    g.browserIsFission = prefs.getBoolPref("fission.autostart", false);
169

170
    g.browserIsIframe = prefs.getBoolPref("reftest.browser.iframe.enabled", false);
171

172
    g.logLevel = prefs.getStringPref("reftest.logLevel", "info");
173

174
175
176
    if (win === undefined || win == null) {
      win = window;
    }
177
178
    if (g.containingWindow == null && win != null) {
      g.containingWindow = win;
179
    }
180

181
182
183
    if (g.browserIsIframe) {
      g.browser = g.containingWindow.document.createElementNS(XHTML_NS, "iframe");
      g.browser.setAttribute("mozbrowser", "");
184
    } else {
185
      g.browser = g.containingWindow.document.createElementNS(XUL_NS, "xul:browser");
186
    }
187
188
189
190
    g.browser.setAttribute("id", "browser");
    g.browser.setAttribute("type", "content");
    g.browser.setAttribute("primary", "true");
    g.browser.setAttribute("remote", g.browserIsRemote ? "true" : "false");
191
192
    // Make sure the browser element is exactly 800x1000, no matter
    // what size our window is
193
    g.browser.setAttribute("style", "padding: 0px; margin: 0px; border:none; min-width: 800px; min-height: 1000px; max-width: 800px; max-height: 1000px");
194

195
    if (Services.appinfo.OS == "Android") {
196
      let doc = g.containingWindow.document.getElementById('main-window');
197
      while (doc.hasChildNodes()) {
198
        doc.firstChild.remove();
199
      }
200
      doc.appendChild(g.browser);
201
202
203
      // TODO Bug 1156817: reftests don't have most of GeckoView infra so we
      // can't register this actor
      ChromeUtils.unregisterWindowActor("LoadURIDelegate");
204
      ChromeUtils.unregisterWindowActor("WebBrowserChrome");
205
    } else {
206
      document.getElementById("reftest-window").appendChild(g.browser);
207
    }
208

209
210
211
212
    // reftests should have the test plugins enabled, not click-to-play
    let plugin1 = getTestPlugin("Test Plug-in");
    let plugin2 = getTestPlugin("Second Test Plug-in");
    if (plugin1 && plugin2) {
213
      g.testPluginEnabledStates = [plugin1.enabledState, plugin2.enabledState];
214
215
      plugin1.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
      plugin2.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
216
    } else {
217
      logger.warning("Could not get test plugin tags.");
218
219
    }

220
    g.browserMessageManager = g.browser.frameLoader.messageManager;
221
222
    // The content script waits for the initial onload, then notifies
    // us.
223
    RegisterMessageListenersAndLoadContentScript(false);
224
225
}

226
function InitAndStartRefTests()
227
{
228
    /* These prefs are optional, so we don't need to spit an error to the log */
229
    try {
230
231
        var prefs = Cc["@mozilla.org/preferences-service;1"].
                    getService(Ci.nsIPrefBranch);
232
    } catch(e) {
233
        logger.error("EXCEPTION: " + e);
234
    }
235

236
237
238
    try {
      prefs.setBoolPref("android.widget_paints_background", false);
    } catch (e) {}
239
240
241
242
243
244
245
    
    // If fission is enabled, then also put data: URIs in the default web process,
    // since most reftests run in the file process, and this will make data:
    // <iframe>s OOP.
    if (g.browserIsFission) {
      prefs.setBoolPref("browser.tabs.remote.dataUriInDefaultWebProcess", true);
    }
246

247
    /* set the g.loadTimeout */
248
    try {
249
        g.loadTimeout = prefs.getIntPref("reftest.timeout");
250
    } catch(e) {
251
        g.loadTimeout = 5 * 60 * 1000; //5 minutes as per bug 479518
252
    }
253

254
255
    /* Get the logfile for android tests */
    try {
256
        var logFile = prefs.getStringPref("reftest.logFile");
257
        if (logFile) {
258
            var f = FileUtils.File(logFile);
259
260
261
262
            var out = FileUtils.openFileOutputStream(f, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE);
            g.logFile = Cc["@mozilla.org/intl/converter-output-stream;1"]
                          .createInstance(Ci.nsIConverterOutputStream);
            g.logFile.init(out, null);
263
        }
264
    } catch(e) {}
265

266
    g.remote = prefs.getBoolPref("reftest.remote", false);
267

268
    g.ignoreWindowSize = prefs.getBoolPref("reftest.ignoreWindowSize", false);
269

L. David Baron's avatar
L. David Baron committed
270
    /* Support for running a chunk (subset) of tests.  In separate try as this is optional */
271
    try {
272
273
        g.totalChunks = prefs.getIntPref("reftest.totalChunks");
        g.thisChunk = prefs.getIntPref("reftest.thisChunk");
274
275
    }
    catch(e) {
276
277
        g.totalChunks = 0;
        g.thisChunk = 0;
278
279
    }

280
    try {
281
        g.focusFilterMode = prefs.getStringPref("reftest.focusFilterMode");
282
283
    } catch(e) {}

284
285
286
287
    try {
        g.isCoverageBuild = prefs.getBoolPref("reftest.isCoverageBuild");
    } catch(e) {}

288
289
290
    try {
        g.compareRetainedDisplayLists = prefs.getBoolPref("reftest.compareRetainedDisplayLists");
    } catch (e) {}
291

292
    try {
293
294
295
        // We have to set print.always_print_silent or a print dialog would
        // appear for each print operation, which would interrupt the test run.
        prefs.setBoolPref("print.always_print_silent", true);
296
297
    } catch (e) {
        /* uh oh, print reftests may not work... */
298
        logger.warning("Failed to set silent printing pref, EXCEPTION: " + e);
299
300
    }

301
    g.windowUtils = g.containingWindow.windowUtils;
302
    if (!g.windowUtils || !g.windowUtils.compareCanvases)
303
        throw "nsIDOMWindowUtils inteface missing";
304

305
306
    g.ioService = Cc[IO_SERVICE_CONTRACTID].getService(Ci.nsIIOService);
    g.debug = Cc[DEBUG_CONTRACTID].getService(Ci.nsIDebug2);
307
308
309

    RegisterProcessCrashObservers();

310
311
    if (g.remote) {
        g.server = null;
312
    } else {
313
        g.server = new HttpServer();
314
    }
315
    try {
316
        if (g.server)
317
318
            StartHTTPServer();
    } catch (ex) {
319
320
        //g.browser.loadURI('data:text/plain,' + ex);
        ++g.testResults.Exception;
321
        logger.error("EXCEPTION: " + ex);
322
323
324
        DoneTests();
    }

325
    // Focus the content browser.
326
    if (g.focusFilterMode != FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS) {
327
328
329
330
        var fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
        if (fm.activeWindow != g.containingWindow) {
            Focus();
        }
331
        g.browser.addEventListener("focus", ReadTests, true);
332
        g.browser.focus();
333
    } else {
334
        ReadTests();
335
    }
336
337
338
339
}

function StartHTTPServer()
{
340
341
342
    g.server.registerContentType("sjs", "sjs");
    g.server.start(-1);
    g.httpServerPort = g.server.identity.primaryPort;
343
344
}

345
346
347
348
349
350
351
352
353
354
355
// Perform a Fisher-Yates shuffle of the array.
function Shuffle(array)
{
    for (var i = array.length - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        var temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

356
357
358
359
360
361
362
function ReadTests() {
    try {
        if (g.focusFilterMode != FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS) {
            g.browser.removeEventListener("focus", ReadTests, true);
        }

        g.urls = [];
363
364
        var prefs = Cc["@mozilla.org/preferences-service;1"].
                    getService(Ci.nsIPrefBranch);
365

366
367
368
369
370
371
372
373
374
375
376
377
378
        /* There are three modes implemented here:
         * 1) reftest.manifests
         * 2) reftest.manifests and reftest.manifests.dumpTests
         * 3) reftest.tests
         *
         * The first will parse the specified manifests, then immediately
         * run the tests. The second will parse the manifests, save the test
         * objects to a file and exit. The third will load a file of test
         * objects and run them.
         *
         * The latter two modes are used to pass test data back and forth
         * with python harness.
        */
379
380
381
        let manifests = prefs.getStringPref("reftest.manifests", null);
        let dumpTests = prefs.getStringPref("reftest.manifests.dumpTests", null);
        let testList = prefs.getStringPref("reftest.tests", null);
382
383
384

        if ((testList && manifests) || !(testList || manifests)) {
            logger.error("Exactly one of reftest.manifests or reftest.tests must be specified.");
385
386
            logger.debug("reftest.manifests is: " + manifests);
            logger.error("reftest.tests is: " + testList);
387
388
389
            DoneTests();
        }

390
        if (testList) {
391
            logger.debug("Reading test objects from: " + testList);
392
393
394
395
            let promise = OS.File.read(testList).then(function onSuccess(array) {
                let decoder = new TextDecoder();
                g.urls = JSON.parse(decoder.decode(array)).map(CreateUrls);
                StartTests();
396
397
398
            }).catch(function onFailure(e) {
                logger.error("Failed to load test objects: " + e);
                DoneTests();
399
400
401
            });
        } else if (manifests) {
            // Parse reftest manifests
402
            logger.debug("Reading " + manifests.length + " manifests");
403
404
            manifests = JSON.parse(manifests);
            g.urlsFilterRegex = manifests[null];
405

406
            var globalFilter = manifests.hasOwnProperty("") ? new RegExp(manifests[""]) : null;
407
            delete manifests[""];
408
            var manifestURLs = Object.keys(manifests);
409

410
411
412
413
414
            // Ensure we read manifests from higher up the directory tree first so that we
            // process includes before reading the included manifest again
            manifestURLs.sort(function(a,b) {return a.length - b.length})
            manifestURLs.forEach(function(manifestURL) {
                logger.info("Reading manifest " + manifestURL);
415
416
417
418
                var manifestInfo = manifests[manifestURL];
                var filter = manifestInfo[0] ? new RegExp(manifestInfo[0]) : null;
                var manifestID = manifestInfo[1];
                ReadTopManifest(manifestURL, [globalFilter, filter, false], manifestID);
419
420
421
            });

            if (dumpTests) {
422
                logger.debug("Dumping test objects to file: " + dumpTests);
423
424
425
426
427
428
429
430
431
432
433
434
                let encoder = new TextEncoder();
                let tests = encoder.encode(JSON.stringify(g.urls));
                OS.File.writeAtomic(dumpTests, tests, {flush: true}).then(
                  function onSuccess() {
                    DoneTests();
                  },
                  function onFailure(reason) {
                    logger.error("failed to write test data: " + reason);
                    DoneTests();
                  }
                )
            } else {
435
                logger.debug("Running " + g.urls.length + " test objects");
436
437
438
439
440
                g.manageSuite = true;
                g.urls = g.urls.map(CreateUrls);
                StartTests();
            }
        }
441
442
443
    } catch(e) {
        ++g.testResults.Exception;
        logger.error("EXCEPTION: " + e);
444
        DoneTests();
445
    }
446
}
447

448
449
function StartTests()
{
450
451
    /* These prefs are optional, so we don't need to spit an error to the log */
    try {
452
453
        var prefs = Cc["@mozilla.org/preferences-service;1"].
                    getService(Ci.nsIPrefBranch);
454
    } catch(e) {
455
        logger.error("EXCEPTION: " + e);
456
    }
457

458
    g.noCanvasCache = prefs.getIntPref("reftest.nocache", false);
459

460
    g.shuffle = prefs.getBoolPref("reftest.shuffle", false);
461

462
    g.runUntilFailure = prefs.getBoolPref("reftest.runUntilFailure", false);
463

464
465
    g.verify = prefs.getBoolPref("reftest.verify", false);

466
    g.cleanupPendingCrashes = prefs.getBoolPref("reftest.cleanupPendingCrashes", false);
467

468
469
470
471
472
    // Check if there are any crash dump files from the startup procedure, before
    // we start running the first test. Otherwise the first test might get
    // blamed for producing a crash dump file when that was not the case.
    CleanUpCrashDumpFiles();

473
    // When we repeat this function is called again, so really only want to set
474
475
476
    // g.repeat once.
    if (g.repeat == null) {
      g.repeat = prefs.getIntPref("reftest.repeat", 0);
477
478
    }

479
    g.runSlowTests = prefs.getIntPref("reftest.skipslowtests", false);
480

481
482
    if (g.shuffle) {
        g.noCanvasCache = true;
483
484
485
    }

    try {
486
        BuildUseCounts();
487

488
489
490
        // Filter tests which will be skipped to get a more even distribution when chunking
        // tURLs is a temporary array containing all active tests
        var tURLs = new Array();
491
        for (var i = 0; i < g.urls.length; ++i) {
492
            if (g.urls[i].skip)
493
494
                continue;

495
            if (g.urls[i].needsFocus && !Focus())
496
497
                continue;

498
            if (g.urls[i].slow && !g.runSlowTests)
499
500
                continue;

501
            tURLs.push(g.urls[i]);
502
503
        }

504
        var numActiveTests = tURLs.length;
505

506
        if (g.totalChunks > 0 && g.thisChunk > 0) {
507
508
            // Calculate start and end indices of this chunk if tURLs array were
            // divided evenly
509
510
511
            var testsPerChunk = tURLs.length / g.totalChunks;
            var start = Math.round((g.thisChunk-1) * testsPerChunk);
            var end = Math.round(g.thisChunk * testsPerChunk);
512
            numActiveTests = end - start;
513

514
515
516
517
            // Map these indices onto the g.urls array. This avoids modifying the
            // g.urls array which prevents skipped tests from showing up in the log
            start = g.thisChunk == 1 ? 0 : g.urls.indexOf(tURLs[start]);
            end = g.thisChunk == g.totalChunks ? g.urls.length : g.urls.indexOf(tURLs[end + 1]) - 1;
518

519
520
            logger.info("Running chunk " + g.thisChunk + " out of " + g.totalChunks + " chunks.  " +
                "tests " + (start+1) + "-" + end + "/" + g.urls.length);
521

522
            g.urls = g.urls.slice(start, end);
523
        }
524

525
        if (g.manageSuite && !g.suiteStarted) {
526
527
528
529
530
531
            var ids = {};
            g.urls.forEach(function(test) {
                if (!(test.manifestID in ids)) {
                    ids[test.manifestID] = [];
                }
                ids[test.manifestID].push(test.identifier);
532
            });
533
            var suite = prefs.getStringPref('reftest.suite', 'reftest');
534
            logger.suiteStart(ids, suite, {"skipped": g.urls.length - numActiveTests});
535
            g.suiteStarted = true
536
537
        }

538
539
        if (g.shuffle) {
            Shuffle(g.urls);
540
541
        }

542
        g.totalTests = g.urls.length;
543
        if (!g.totalTests && !g.verify && !g.repeat)
544
545
            throw "No tests to run";

546
        g.uriCanvases = {};
547
548
549
550
551
552
553

        PerTestCoverageUtils.beforeTest()
        .then(StartCurrentTest)
        .catch(e => {
            logger.error("EXCEPTION: " + e);
            DoneTests();
        });
554
    } catch (ex) {
555
556
        //g.browser.loadURI('data:text/plain,' + ex);
        ++g.testResults.Exception;
557
        logger.error("EXCEPTION: " + ex);
558
        DoneTests();
559
560
561
562
563
    }
}

function OnRefTestUnload()
{
564
565
566
  let plugin1 = getTestPlugin("Test Plug-in");
  let plugin2 = getTestPlugin("Second Test Plug-in");
  if (plugin1 && plugin2) {
567
568
    plugin1.enabledState = g.testPluginEnabledStates[0];
    plugin2.enabledState = g.testPluginEnabledStates[1];
569
  } else {
570
    logger.warning("Failed to get test plugin tags.");
571
  }
572
573
}

574
function AddURIUseCount(uri)
575
576
577
578
{
    if (uri == null)
        return;

579
    var spec = uri.spec;
580
581
    if (spec in g.uriUseCounts) {
        g.uriUseCounts[spec]++;
582
    } else {
583
        g.uriUseCounts[spec] = 1;
584
585
586
587
588
    }
}

function BuildUseCounts()
{
589
    if (g.noCanvasCache) {
590
591
592
        return;
    }

593
594
595
    g.uriUseCounts = {};
    for (var i = 0; i < g.urls.length; ++i) {
        var url = g.urls[i];
596
        if (!url.skip &&
597
            (url.type == TYPE_REFTEST_EQUAL ||
598
599
             url.type == TYPE_REFTEST_NOTEQUAL)) {
            if (url.prefSettings1.length == 0) {
600
                AddURIUseCount(g.urls[i].url1);
601
602
            }
            if (url.prefSettings2.length == 0) {
603
                AddURIUseCount(g.urls[i].url2);
604
            }
605
606
607
608
        }
    }
}

609
610
611
// Return true iff this window is focused when this function returns.
function Focus()
{
612
    var fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
613
    fm.focusedWindow = g.containingWindow;
614
#ifdef XP_MACOSX
615
    try {
616
        var dock = Cc["@mozilla.org/widget/macdocksupport;1"].getService(Ci.nsIMacDockSupport);
617
618
619
        dock.activateApplication(true);
    } catch(ex) {
    }
620
#endif // XP_MACOSX
621
622
623
    return true;
}

624
625
626
627
628
629
function Blur()
{
    // On non-remote reftests, this will transfer focus to the dummy window
    // we created to hold focus for non-needs-focus tests.  Buggy tests
    // (ones which require focus but don't request needs-focus) will then
    // fail.
630
    g.containingWindow.blur();
631
632
}

633
634
function StartCurrentTest()
{
635
    g.testLog = [];
636

637
    // make sure we don't run tests that are expected to kill the browser
638
639
    while (g.urls.length > 0) {
        var test = g.urls[0];
640
        logger.testStart(test.identifier);
641
        if (test.skip) {
642
            ++g.testResults.Skip;
643
            logger.testEnd(test.identifier, "SKIP");
644
            g.urls.shift();
645
        } else if (test.needsFocus && !Focus()) {
646
647
            // FIXME: Marking this as a known fail is dangerous!  What
            // if it starts failing all the time?
648
            ++g.testResults.Skip;
649
            logger.testEnd(test.identifier, "SKIP", null, "(COULDN'T GET FOCUS)");
650
651
652
            g.urls.shift();
        } else if (test.slow && !g.runSlowTests) {
            ++g.testResults.Slow;
653
            logger.testEnd(test.identifier, "SKIP", null, "(SLOW)");
654
            g.urls.shift();
655
656
657
        } else {
            break;
        }
658
659
    }

660
661
    if ((g.urls.length == 0 && g.repeat == 0) ||
        (g.runUntilFailure && HasUnexpectedResult())) {
662
        RestoreChangedPreferences();
663
        DoneTests();
664
    } else if (g.urls.length == 0 && g.repeat > 0) {
665
        // Repeat
666
        g.repeat--;
667
        ReadTests();
668
    } else {
669
670
        if (g.urls[0].chaosMode) {
            g.windowUtils.enterChaosMode();
671
        }
672
        if (!g.urls[0].needsFocus) {
673
674
            Blur();
        }
675
676
677
        var currentTest = g.totalTests - g.urls.length;
        g.containingWindow.document.title = "reftest: " + currentTest + " / " + g.totalTests +
            " (" + Math.floor(100 * (currentTest / g.totalTests)) + "%)";
678
        StartCurrentURI(URL_TARGET_TYPE_TEST);
679
    }
680
681
}

682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
// A simplified version of the function with the same name in tabbrowser.js.
function updateBrowserRemotenessByURL(aBrowser, aURL) {
  let remoteType = E10SUtils.getRemoteTypeForURI(
    aURL,
    aBrowser.ownerGlobal.docShell.nsILoadContext.useRemoteTabs,
    aBrowser.ownerGlobal.docShell.nsILoadContext.useRemoteSubframes,
    aBrowser.remoteType,
    aBrowser.currentURI
  );
  // Things get confused if we switch to not-remote
  // for chrome:// URIs, so lets not for now.
  if (remoteType == E10SUtils.NOT_REMOTE &&
      g.browserIsRemote) {
    remoteType = aBrowser.remoteType;
  }
  if (aBrowser.remoteType != remoteType) {
    if (remoteType == E10SUtils.NOT_REMOTE) {
      aBrowser.removeAttribute("remote");
      aBrowser.removeAttribute("remoteType");
    } else {
      aBrowser.setAttribute("remote", "true");
      aBrowser.setAttribute("remoteType", remoteType);
    }
    aBrowser.changeRemoteness({ remoteType });
    aBrowser.construct();

    g.browserMessageManager = aBrowser.frameLoader.messageManager;
    RegisterMessageListenersAndLoadContentScript(true);
    return new Promise(resolve => { g.resolveContentReady = resolve;  });
  }

  return Promise.resolve();
}

async function StartCurrentURI(aURLTargetType)
717
{
718
719
720
721
    const isStartingRef = (aURLTargetType == URL_TARGET_TYPE_REFERENCE);

    g.currentURL = g.urls[0][isStartingRef ? "url2" : "url1"].spec;
    g.currentURLTargetType = aURLTargetType;
722

723
724
    RestoreChangedPreferences();

725
726
    var prefs = Cc["@mozilla.org/preferences-service;1"].
        getService(Ci.nsIPrefBranch);
727

728
729
730
    const prefSettings =
      g.urls[0][isStartingRef ? "prefSettings2" : "prefSettings1"];

731
732
733
734
735
736
737
738
739
740
741
742
743
744
    if (prefSettings.length > 0) {
        var badPref = undefined;
        try {
            prefSettings.forEach(function(ps) {
                var oldVal;
                if (ps.type == PREF_BOOLEAN) {
                    try {
                        oldVal = prefs.getBoolPref(ps.name);
                    } catch (e) {
                        badPref = "boolean preference '" + ps.name + "'";
                        throw "bad pref";
                    }
                } else if (ps.type == PREF_STRING) {
                    try {
745
                        oldVal = prefs.getStringPref(ps.name);
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
                    } catch (e) {
                        badPref = "string preference '" + ps.name + "'";
                        throw "bad pref";
                    }
                } else if (ps.type == PREF_INTEGER) {
                    try {
                        oldVal = prefs.getIntPref(ps.name);
                    } catch (e) {
                        badPref = "integer preference '" + ps.name + "'";
                        throw "bad pref";
                    }
                } else {
                    throw "internal error - unknown preference type";
                }
                if (oldVal != ps.value) {
761
                    g.prefsToRestore.push( { name: ps.name,
762
763
764
765
766
767
                                            type: ps.type,
                                            value: oldVal } );
                    var value = ps.value;
                    if (ps.type == PREF_BOOLEAN) {
                        prefs.setBoolPref(ps.name, value);
                    } else if (ps.type == PREF_STRING) {
768
                        prefs.setStringPref(ps.name, value);
769
770
771
772
                        value = '"' + value + '"';
                    } else if (ps.type == PREF_INTEGER) {
                        prefs.setIntPref(ps.name, value);
                    }
773
                    logger.info("SET PREFERENCE pref(" + ps.name + "," + value + ")");
774
775
776
777
                }
            });
        } catch (e) {
            if (e == "bad pref") {
778
                var test = g.urls[0];
779
                if (test.expected == EXPECTED_FAIL) {
780
781
                    logger.testEnd(test.identifier, "FAIL", "FAIL",
                                   "(SKIPPED; " + badPref + " not known or wrong type)");
782
                    ++g.testResults.Skip;
783
                } else {
784
785
                    logger.testEnd(test.identifier, "FAIL", "PASS",
                                   badPref + " not known or wrong type");
786
                    ++g.testResults.UnexpectedFail;
787
                }
788
789

                // skip the test that had a bad preference
790
                g.urls.shift();
791
792
                StartCurrentTest();
                return;
793
794
795
796
797
798
799
            } else {
                throw e;
            }
        }
    }

    if (prefSettings.length == 0 &&
800
801
802
803
        g.uriCanvases[g.currentURL] &&
        (g.urls[0].type == TYPE_REFTEST_EQUAL ||
         g.urls[0].type == TYPE_REFTEST_NOTEQUAL) &&
        g.urls[0].maxAsserts == 0) {
804
        // Pretend the document loaded --- RecordResult will notice
805
        // there's already a canvas for this URL
806
        g.containingWindow.setTimeout(RecordResult, 0);
807
    } else {
808
        var currentTest = g.totalTests - g.urls.length;
809
810
        // Log this to preserve the same overall log format,
        // should be removed if the format is updated
811
812
813
        gDumpFn("REFTEST TEST-LOAD | " + g.currentURL + " | " + currentTest + " / " + g.totalTests +
                " (" + Math.floor(100 * (currentTest / g.totalTests)) + "%)\n");
        TestBuffer("START " + g.currentURL);
814
815
        await updateBrowserRemotenessByURL(g.browser, g.currentURL);

816
        var type = g.urls[0].type
817
        if (TYPE_SCRIPT == type) {
818
            SendLoadScriptTest(g.currentURL, g.loadTimeout);
819
        } else if (TYPE_PRINT == type) {
820
            SendLoadPrintTest(g.currentURL, g.loadTimeout);
821
        } else {
822
            SendLoadTest(type, g.currentURL, g.currentURLTargetType, g.loadTimeout);
823
        }
824
    }
825
826
827
828
}

function DoneTests()
{
829
830
831
832
833
834
835
836
837
838
839
840
841
842
    PerTestCoverageUtils.afterTest()
    .catch(e => logger.error("EXCEPTION: " + e))
    .then(() => {
        if (g.manageSuite) {
            g.suiteStarted = false
            logger.suiteEnd({'results': g.testResults});
        } else {
            logger._logData('results', {results: g.testResults});
        }
        logger.info("Slowest test took " + g.slowestTestTime + "ms (" + g.slowestTestURL + ")");
        logger.info("Total canvas count = " + g.recycledCanvases.length);
        if (g.failedUseWidgetLayers) {
            LogWidgetLayersFailure();
        }
843