1/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- / 2/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ 3/* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6"use strict"; 7 8var EXPORTED_SYMBOLS = [ 9 "OnRefTestLoad", 10 "OnRefTestUnload", 11]; 12 13Cu.import("resource://gre/modules/FileUtils.jsm"); 14Cu.import("resource://reftest/globals.jsm", this); 15Cu.import("resource://reftest/httpd.jsm", this); 16Cu.import("resource://reftest/manifest.jsm", this); 17Cu.import("resource://reftest/StructuredLog.jsm", this); 18Cu.import("resource://reftest/PerTestCoverageUtils.jsm", this); 19Cu.import("resource://gre/modules/Services.jsm"); 20Cu.import('resource://gre/modules/XPCOMUtils.jsm'); 21 22const { E10SUtils } = ChromeUtils.import( 23 "resource://gre/modules/E10SUtils.jsm" 24); 25 26XPCOMUtils.defineLazyGetter(this, "OS", function() { 27 const { OS } = Cu.import("resource://gre/modules/osfile.jsm"); 28 return OS; 29}); 30 31function HasUnexpectedResult() 32{ 33 return g.testResults.Exception > 0 || 34 g.testResults.FailedLoad > 0 || 35 g.testResults.UnexpectedFail > 0 || 36 g.testResults.UnexpectedPass > 0 || 37 g.testResults.AssertionUnexpected > 0 || 38 g.testResults.AssertionUnexpectedFixed > 0; 39} 40 41// By default we just log to stdout 42var gDumpFn = function(line) { 43 dump(line); 44 if (g.logFile) { 45 g.logFile.writeString(line); 46 } 47} 48var gDumpRawLog = function(record) { 49 // Dump JSON representation of data on a single line 50 var line = "\n" + JSON.stringify(record) + "\n"; 51 dump(line); 52 53 if (g.logFile) { 54 g.logFile.writeString(line); 55 } 56} 57g.logger = new StructuredLogger('reftest', gDumpRawLog); 58var logger = g.logger; 59 60function TestBuffer(str) 61{ 62 logger.debug(str); 63 g.testLog.push(str); 64} 65 66function isWebRenderOnAndroidDevice() { 67 var xr = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); 68 // This is the best we can do for now; maybe in the future we'll have 69 // more correct detection of this case. 70 return xr.OS == "Android" && 71 g.browserIsRemote && 72 g.windowUtils.layerManagerType.startsWith("WebRender"); 73} 74 75function FlushTestBuffer() 76{ 77 // In debug mode, we've dumped all these messages already. 78 if (g.logLevel !== 'debug') { 79 for (var i = 0; i < g.testLog.length; ++i) { 80 logger.info("Saved log: " + g.testLog[i]); 81 } 82 } 83 g.testLog = []; 84} 85 86function LogWidgetLayersFailure() 87{ 88 logger.error( 89 "Screen resolution is too low - USE_WIDGET_LAYERS was disabled. " + 90 (g.browserIsRemote ? 91 "Since E10s is enabled, there is no fallback rendering path!" : 92 "The fallback rendering path is not reliably consistent with on-screen rendering.")); 93 94 logger.error( 95 "If you cannot increase your screen resolution you can try reducing " + 96 "gecko's pixel scaling by adding something like '--setpref " + 97 "layout.css.devPixelsPerPx=1.0' to your './mach reftest' command " + 98 "(possibly as an alias in ~/.mozbuild/machrc). Note that this is " + 99 "inconsistent with CI testing, and may interfere with HighDPI/" + 100 "reftest-zoom tests."); 101} 102 103function AllocateCanvas() 104{ 105 if (g.recycledCanvases.length > 0) { 106 return g.recycledCanvases.shift(); 107 } 108 109 var canvas = g.containingWindow.document.createElementNS(XHTML_NS, "canvas"); 110 var r = g.browser.getBoundingClientRect(); 111 canvas.setAttribute("width", Math.ceil(r.width)); 112 canvas.setAttribute("height", Math.ceil(r.height)); 113 114 return canvas; 115} 116 117function ReleaseCanvas(canvas) 118{ 119 // store a maximum of 2 canvases, if we're not caching 120 if (!g.noCanvasCache || g.recycledCanvases.length < 2) { 121 g.recycledCanvases.push(canvas); 122 } 123} 124 125function IDForEventTarget(event) 126{ 127 try { 128 return "'" + event.target.getAttribute('id') + "'"; 129 } catch (ex) { 130 return "<unknown>"; 131 } 132} 133 134function OnRefTestLoad(win) 135{ 136 g.crashDumpDir = Cc[NS_DIRECTORY_SERVICE_CONTRACTID] 137 .getService(Ci.nsIProperties) 138 .get("ProfD", Ci.nsIFile); 139 g.crashDumpDir.append("minidumps"); 140 141 g.pendingCrashDumpDir = Cc[NS_DIRECTORY_SERVICE_CONTRACTID] 142 .getService(Ci.nsIProperties) 143 .get("UAppData", Ci.nsIFile); 144 g.pendingCrashDumpDir.append("Crash Reports"); 145 g.pendingCrashDumpDir.append("pending"); 146 147 var env = Cc["@mozilla.org/process/environment;1"]. 148 getService(Ci.nsIEnvironment); 149 150 g.browserIsRemote = Services.appinfo.browserTabsRemoteAutostart; 151 g.browserIsFission = Services.appinfo.fissionAutostart; 152 153 var prefs = Cc["@mozilla.org/preferences-service;1"]. 154 getService(Ci.nsIPrefBranch); 155 g.browserIsIframe = prefs.getBoolPref("reftest.browser.iframe.enabled", false); 156 g.useDrawSnapshot = prefs.getBoolPref("reftest.use-draw-snapshot", false); 157 158 g.logLevel = prefs.getStringPref("reftest.logLevel", "info"); 159 160 if (win === undefined || win == null) { 161 win = window; 162 } 163 if (g.containingWindow == null && win != null) { 164 g.containingWindow = win; 165 } 166 167 if (g.browserIsIframe) { 168 g.browser = g.containingWindow.document.createElementNS(XHTML_NS, "iframe"); 169 g.browser.setAttribute("mozbrowser", ""); 170 } else { 171 g.browser = g.containingWindow.document.createElementNS(XUL_NS, "xul:browser"); 172 } 173 g.browser.setAttribute("id", "browser"); 174 g.browser.setAttribute("type", "content"); 175 g.browser.setAttribute("primary", "true"); 176 g.browser.setAttribute("remote", g.browserIsRemote ? "true" : "false"); 177 // Make sure the browser element is exactly 800x1000, no matter 178 // what size our window is 179 g.browser.setAttribute("style", "padding: 0px; margin: 0px; border:none; min-width: 800px; min-height: 1000px; max-width: 800px; max-height: 1000px"); 180 181 if (Services.appinfo.OS == "Android") { 182 let doc = g.containingWindow.document.getElementById('main-window'); 183 while (doc.hasChildNodes()) { 184 doc.firstChild.remove(); 185 } 186 doc.appendChild(g.browser); 187 // TODO Bug 1156817: reftests don't have most of GeckoView infra so we 188 // can't register this actor 189 ChromeUtils.unregisterWindowActor("LoadURIDelegate"); 190 } else { 191 document.getElementById("reftest-window").appendChild(g.browser); 192 } 193 194 g.browserMessageManager = g.browser.frameLoader.messageManager; 195 // The content script waits for the initial onload, then notifies 196 // us. 197 RegisterMessageListenersAndLoadContentScript(false); 198} 199 200function InitAndStartRefTests() 201{ 202 /* These prefs are optional, so we don't need to spit an error to the log */ 203 try { 204 var prefs = Cc["@mozilla.org/preferences-service;1"]. 205 getService(Ci.nsIPrefBranch); 206 } catch(e) { 207 logger.error("EXCEPTION: " + e); 208 } 209 210 try { 211 prefs.setBoolPref("android.widget_paints_background", false); 212 } catch (e) {} 213 214 // If fission is enabled, then also put data: URIs in the default web process, 215 // since most reftests run in the file process, and this will make data: 216 // <iframe>s OOP. 217 if (g.browserIsFission) { 218 prefs.setBoolPref("browser.tabs.remote.dataUriInDefaultWebProcess", true); 219 } 220 221 /* set the g.loadTimeout */ 222 try { 223 g.loadTimeout = prefs.getIntPref("reftest.timeout"); 224 } catch(e) { 225 g.loadTimeout = 5 * 60 * 1000; //5 minutes as per bug 479518 226 } 227 228 /* Get the logfile for android tests */ 229 try { 230 var logFile = prefs.getStringPref("reftest.logFile"); 231 if (logFile) { 232 var f = FileUtils.File(logFile); 233 var out = FileUtils.openFileOutputStream(f, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE); 234 g.logFile = Cc["@mozilla.org/intl/converter-output-stream;1"] 235 .createInstance(Ci.nsIConverterOutputStream); 236 g.logFile.init(out, null); 237 } 238 } catch(e) {} 239 240 g.remote = prefs.getBoolPref("reftest.remote", false); 241 242 g.ignoreWindowSize = prefs.getBoolPref("reftest.ignoreWindowSize", false); 243 244 /* Support for running a chunk (subset) of tests. In separate try as this is optional */ 245 try { 246 g.totalChunks = prefs.getIntPref("reftest.totalChunks"); 247 g.thisChunk = prefs.getIntPref("reftest.thisChunk"); 248 } 249 catch(e) { 250 g.totalChunks = 0; 251 g.thisChunk = 0; 252 } 253 254 try { 255 g.focusFilterMode = prefs.getStringPref("reftest.focusFilterMode"); 256 } catch(e) {} 257 258 try { 259 g.isCoverageBuild = prefs.getBoolPref("reftest.isCoverageBuild"); 260 } catch(e) {} 261 262 try { 263 g.compareRetainedDisplayLists = prefs.getBoolPref("reftest.compareRetainedDisplayLists"); 264 } catch (e) {} 265 266 try { 267 // We have to set print.always_print_silent or a print dialog would 268 // appear for each print operation, which would interrupt the test run. 269 prefs.setBoolPref("print.always_print_silent", true); 270 } catch (e) { 271 /* uh oh, print reftests may not work... */ 272 logger.warning("Failed to set silent printing pref, EXCEPTION: " + e); 273 } 274 275 g.windowUtils = g.containingWindow.windowUtils; 276 if (!g.windowUtils || !g.windowUtils.compareCanvases) 277 throw "nsIDOMWindowUtils inteface missing"; 278 279 g.ioService = Cc[IO_SERVICE_CONTRACTID].getService(Ci.nsIIOService); 280 g.debug = Cc[DEBUG_CONTRACTID].getService(Ci.nsIDebug2); 281 282 RegisterProcessCrashObservers(); 283 284 if (g.remote) { 285 g.server = null; 286 } else { 287 g.server = new HttpServer(); 288 } 289 try { 290 if (g.server) 291 StartHTTPServer(); 292 } catch (ex) { 293 //g.browser.loadURI('data:text/plain,' + ex); 294 ++g.testResults.Exception; 295 logger.error("EXCEPTION: " + ex); 296 DoneTests(); 297 } 298 299 // Focus the content browser. 300 if (g.focusFilterMode != FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS) { 301 var fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); 302 if (fm.activeWindow != g.containingWindow) { 303 Focus(); 304 } 305 g.browser.addEventListener("focus", ReadTests, true); 306 g.browser.focus(); 307 } else { 308 ReadTests(); 309 } 310} 311 312function StartHTTPServer() 313{ 314 g.server.registerContentType("sjs", "sjs"); 315 g.server.start(-1); 316 g.httpServerPort = g.server.identity.primaryPort; 317} 318 319// Perform a Fisher-Yates shuffle of the array. 320function Shuffle(array) 321{ 322 for (var i = array.length - 1; i > 0; i--) { 323 var j = Math.floor(Math.random() * (i + 1)); 324 var temp = array[i]; 325 array[i] = array[j]; 326 array[j] = temp; 327 } 328} 329 330function ReadTests() { 331 try { 332 if (g.focusFilterMode != FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS) { 333 g.browser.removeEventListener("focus", ReadTests, true); 334 } 335 336 g.urls = []; 337 var prefs = Cc["@mozilla.org/preferences-service;1"]. 338 getService(Ci.nsIPrefBranch); 339 340 /* There are three modes implemented here: 341 * 1) reftest.manifests 342 * 2) reftest.manifests and reftest.manifests.dumpTests 343 * 3) reftest.tests 344 * 345 * The first will parse the specified manifests, then immediately 346 * run the tests. The second will parse the manifests, save the test 347 * objects to a file and exit. The third will load a file of test 348 * objects and run them. 349 * 350 * The latter two modes are used to pass test data back and forth 351 * with python harness. 352 */ 353 let manifests = prefs.getStringPref("reftest.manifests", null); 354 let dumpTests = prefs.getStringPref("reftest.manifests.dumpTests", null); 355 let testList = prefs.getStringPref("reftest.tests", null); 356 357 if ((testList && manifests) || !(testList || manifests)) { 358 logger.error("Exactly one of reftest.manifests or reftest.tests must be specified."); 359 logger.debug("reftest.manifests is: " + manifests); 360 logger.error("reftest.tests is: " + testList); 361 DoneTests(); 362 } 363 364 if (testList) { 365 logger.debug("Reading test objects from: " + testList); 366 let promise = OS.File.read(testList).then(function onSuccess(array) { 367 let decoder = new TextDecoder(); 368 g.urls = JSON.parse(decoder.decode(array)).map(CreateUrls); 369 StartTests(); 370 }).catch(function onFailure(e) { 371 logger.error("Failed to load test objects: " + e); 372 DoneTests(); 373 }); 374 } else if (manifests) { 375 // Parse reftest manifests 376 logger.debug("Reading " + manifests.length + " manifests"); 377 manifests = JSON.parse(manifests); 378 g.urlsFilterRegex = manifests[null]; 379 380 var globalFilter = null; 381 if (manifests.hasOwnProperty("")) { 382 let filterAndId = manifests[""]; 383 if (!Array.isArray(filterAndId)) { 384 logger.error(`manifest[""] should be an array`); 385 DoneTests(); 386 } 387 if (filterAndId.length === 0) { 388 logger.error(`manifest[""] should contain a filter pattern in the 1st item`); 389 DoneTests(); 390 } 391 let filter = filterAndId[0]; 392 if (typeof filter !== "string") { 393 logger.error(`The first item of manifest[""] should be a string`); 394 DoneTests(); 395 } 396 globalFilter = new RegExp(filter); 397 delete manifests[""]; 398 } 399 400 var manifestURLs = Object.keys(manifests); 401 402 // Ensure we read manifests from higher up the directory tree first so that we 403 // process includes before reading the included manifest again 404 manifestURLs.sort(function(a,b) {return a.length - b.length}) 405 manifestURLs.forEach(function(manifestURL) { 406 logger.info("Reading manifest " + manifestURL); 407 var manifestInfo = manifests[manifestURL]; 408 var filter = manifestInfo[0] ? new RegExp(manifestInfo[0]) : null; 409 var manifestID = manifestInfo[1]; 410 ReadTopManifest(manifestURL, [globalFilter, filter, false], manifestID); 411 }); 412 413 if (dumpTests) { 414 logger.debug("Dumping test objects to file: " + dumpTests); 415 let encoder = new TextEncoder(); 416 let tests = encoder.encode(JSON.stringify(g.urls)); 417 OS.File.writeAtomic(dumpTests, tests, {flush: true}).then( 418 function onSuccess() { 419 DoneTests(); 420 }, 421 function onFailure(reason) { 422 logger.error("failed to write test data: " + reason); 423 DoneTests(); 424 } 425 ) 426 } else { 427 logger.debug("Running " + g.urls.length + " test objects"); 428 g.manageSuite = true; 429 g.urls = g.urls.map(CreateUrls); 430 StartTests(); 431 } 432 } 433 } catch(e) { 434 ++g.testResults.Exception; 435 logger.error("EXCEPTION: " + e); 436 DoneTests(); 437 } 438} 439 440function StartTests() 441{ 442 /* These prefs are optional, so we don't need to spit an error to the log */ 443 try { 444 var prefs = Cc["@mozilla.org/preferences-service;1"]. 445 getService(Ci.nsIPrefBranch); 446 } catch(e) { 447 logger.error("EXCEPTION: " + e); 448 } 449 450 g.noCanvasCache = prefs.getIntPref("reftest.nocache", false); 451 452 g.shuffle = prefs.getBoolPref("reftest.shuffle", false); 453 454 g.runUntilFailure = prefs.getBoolPref("reftest.runUntilFailure", false); 455 456 g.verify = prefs.getBoolPref("reftest.verify", false); 457 458 g.cleanupPendingCrashes = prefs.getBoolPref("reftest.cleanupPendingCrashes", false); 459 460 // Check if there are any crash dump files from the startup procedure, before 461 // we start running the first test. Otherwise the first test might get 462 // blamed for producing a crash dump file when that was not the case. 463 CleanUpCrashDumpFiles(); 464 465 // When we repeat this function is called again, so really only want to set 466 // g.repeat once. 467 if (g.repeat == null) { 468 g.repeat = prefs.getIntPref("reftest.repeat", 0); 469 } 470 471 g.runSlowTests = prefs.getIntPref("reftest.skipslowtests", false); 472 473 if (g.shuffle) { 474 g.noCanvasCache = true; 475 } 476 477 try { 478 BuildUseCounts(); 479 480 // Filter tests which will be skipped to get a more even distribution when chunking 481 // tURLs is a temporary array containing all active tests 482 var tURLs = new Array(); 483 for (var i = 0; i < g.urls.length; ++i) { 484 if (g.urls[i].skip) 485 continue; 486 487 if (g.urls[i].needsFocus && !Focus()) 488 continue; 489 490 if (g.urls[i].slow && !g.runSlowTests) 491 continue; 492 493 tURLs.push(g.urls[i]); 494 } 495 496 var numActiveTests = tURLs.length; 497 498 if (g.totalChunks > 0 && g.thisChunk > 0) { 499 // Calculate start and end indices of this chunk if tURLs array were 500 // divided evenly 501 var testsPerChunk = tURLs.length / g.totalChunks; 502 var start = Math.round((g.thisChunk-1) * testsPerChunk); 503 var end = Math.round(g.thisChunk * testsPerChunk); 504 numActiveTests = end - start; 505 506 // Map these indices onto the g.urls array. This avoids modifying the 507 // g.urls array which prevents skipped tests from showing up in the log 508 start = g.thisChunk == 1 ? 0 : g.urls.indexOf(tURLs[start]); 509 end = g.thisChunk == g.totalChunks ? g.urls.length : g.urls.indexOf(tURLs[end + 1]) - 1; 510 511 logger.info("Running chunk " + g.thisChunk + " out of " + g.totalChunks + " chunks. " + 512 "tests " + (start+1) + "-" + end + "/" + g.urls.length); 513 514 g.urls = g.urls.slice(start, end); 515 } 516 517 if (g.manageSuite && !g.suiteStarted) { 518 var ids = {}; 519 g.urls.forEach(function(test) { 520 if (!(test.manifestID in ids)) { 521 ids[test.manifestID] = []; 522 } 523 ids[test.manifestID].push(test.identifier); 524 }); 525 var suite = prefs.getStringPref('reftest.suite', 'reftest'); 526 logger.suiteStart(ids, suite, {"skipped": g.urls.length - numActiveTests}); 527 g.suiteStarted = true 528 } 529 530 if (g.shuffle) { 531 Shuffle(g.urls); 532 } 533 534 g.totalTests = g.urls.length; 535 if (!g.totalTests && !g.verify && !g.repeat) 536 throw "No tests to run"; 537 538 g.uriCanvases = {}; 539 540 PerTestCoverageUtils.beforeTest() 541 .then(StartCurrentTest) 542 .catch(e => { 543 logger.error("EXCEPTION: " + e); 544 DoneTests(); 545 }); 546 } catch (ex) { 547 //g.browser.loadURI('data:text/plain,' + ex); 548 ++g.testResults.Exception; 549 logger.error("EXCEPTION: " + ex); 550 DoneTests(); 551 } 552} 553 554function OnRefTestUnload() 555{ 556} 557 558function AddURIUseCount(uri) 559{ 560 if (uri == null) 561 return; 562 563 var spec = uri.spec; 564 if (spec in g.uriUseCounts) { 565 g.uriUseCounts[spec]++; 566 } else { 567 g.uriUseCounts[spec] = 1; 568 } 569} 570 571function BuildUseCounts() 572{ 573 if (g.noCanvasCache) { 574 return; 575 } 576 577 g.uriUseCounts = {}; 578 for (var i = 0; i < g.urls.length; ++i) { 579 var url = g.urls[i]; 580 if (!url.skip && 581 (url.type == TYPE_REFTEST_EQUAL || 582 url.type == TYPE_REFTEST_NOTEQUAL)) { 583 if (url.prefSettings1.length == 0) { 584 AddURIUseCount(g.urls[i].url1); 585 } 586 if (url.prefSettings2.length == 0) { 587 AddURIUseCount(g.urls[i].url2); 588 } 589 } 590 } 591} 592 593// Return true iff this window is focused when this function returns. 594function Focus() 595{ 596 var fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); 597 fm.focusedWindow = g.containingWindow; 598#ifdef XP_MACOSX 599 try { 600 var dock = Cc["@mozilla.org/widget/macdocksupport;1"].getService(Ci.nsIMacDockSupport); 601 dock.activateApplication(true); 602 } catch(ex) { 603 } 604#endif // XP_MACOSX 605 return true; 606} 607 608function Blur() 609{ 610 // On non-remote reftests, this will transfer focus to the dummy window 611 // we created to hold focus for non-needs-focus tests. Buggy tests 612 // (ones which require focus but don't request needs-focus) will then 613 // fail. 614 g.containingWindow.blur(); 615} 616 617function StartCurrentTest() 618{ 619 g.testLog = []; 620 621 // make sure we don't run tests that are expected to kill the browser 622 while (g.urls.length > 0) { 623 var test = g.urls[0]; 624 logger.testStart(test.identifier); 625 if (test.skip) { 626 ++g.testResults.Skip; 627 logger.testEnd(test.identifier, "SKIP"); 628 g.urls.shift(); 629 } else if (test.needsFocus && !Focus()) { 630 // FIXME: Marking this as a known fail is dangerous! What 631 // if it starts failing all the time? 632 ++g.testResults.Skip; 633 logger.testEnd(test.identifier, "SKIP", null, "(COULDN'T GET FOCUS)"); 634 g.urls.shift(); 635 } else if (test.slow && !g.runSlowTests) { 636 ++g.testResults.Slow; 637 logger.testEnd(test.identifier, "SKIP", null, "(SLOW)"); 638 g.urls.shift(); 639 } else { 640 break; 641 } 642 } 643 644 if ((g.urls.length == 0 && g.repeat == 0) || 645 (g.runUntilFailure && HasUnexpectedResult())) { 646 RestoreChangedPreferences(); 647 DoneTests(); 648 } else if (g.urls.length == 0 && g.repeat > 0) { 649 // Repeat 650 g.repeat--; 651 ReadTests(); 652 } else { 653 if (g.urls[0].chaosMode) { 654 g.windowUtils.enterChaosMode(); 655 } 656 if (!g.urls[0].needsFocus) { 657 Blur(); 658 } 659 var currentTest = g.totalTests - g.urls.length; 660 g.containingWindow.document.title = "reftest: " + currentTest + " / " + g.totalTests + 661 " (" + Math.floor(100 * (currentTest / g.totalTests)) + "%)"; 662 StartCurrentURI(URL_TARGET_TYPE_TEST); 663 } 664} 665 666// A simplified version of the function with the same name in tabbrowser.js. 667function updateBrowserRemotenessByURL(aBrowser, aURL) { 668 var oa = E10SUtils.predictOriginAttributes({ browser: aBrowser }); 669 let remoteType = E10SUtils.getRemoteTypeForURI( 670 aURL, 671 aBrowser.ownerGlobal.docShell.nsILoadContext.useRemoteTabs, 672 aBrowser.ownerGlobal.docShell.nsILoadContext.useRemoteSubframes, 673 aBrowser.remoteType, 674 aBrowser.currentURI, 675 oa 676 ); 677 // Things get confused if we switch to not-remote 678 // for chrome:// URIs, so lets not for now. 679 if (remoteType == E10SUtils.NOT_REMOTE && 680 g.browserIsRemote) { 681 remoteType = aBrowser.remoteType; 682 } 683 if (aBrowser.remoteType != remoteType) { 684 if (remoteType == E10SUtils.NOT_REMOTE) { 685 aBrowser.removeAttribute("remote"); 686 aBrowser.removeAttribute("remoteType"); 687 } else { 688 aBrowser.setAttribute("remote", "true"); 689 aBrowser.setAttribute("remoteType", remoteType); 690 } 691 aBrowser.changeRemoteness({ remoteType }); 692 aBrowser.construct(); 693 694 g.browserMessageManager = aBrowser.frameLoader.messageManager; 695 RegisterMessageListenersAndLoadContentScript(true); 696 return new Promise(resolve => { g.resolveContentReady = resolve; }); 697 } 698 699 return Promise.resolve(); 700} 701 702async function StartCurrentURI(aURLTargetType) 703{ 704 const isStartingRef = (aURLTargetType == URL_TARGET_TYPE_REFERENCE); 705 706 g.currentURL = g.urls[0][isStartingRef ? "url2" : "url1"].spec; 707 g.currentURLTargetType = aURLTargetType; 708 709 RestoreChangedPreferences(); 710 711 var prefs = Cc["@mozilla.org/preferences-service;1"]. 712 getService(Ci.nsIPrefBranch); 713 714 const prefSettings = 715 g.urls[0][isStartingRef ? "prefSettings2" : "prefSettings1"]; 716 717 if (prefSettings.length > 0) { 718 var badPref = undefined; 719 try { 720 prefSettings.forEach(function(ps) { 721 let prefExists = false; 722 try { 723 let prefType = prefs.getPrefType(ps.name); 724 prefExists = (prefType != prefs.PREF_INVALID); 725 } catch (e) { 726 } 727 if (!prefExists) { 728 logger.info("Pref " + ps.name + " not found, will be added"); 729 } 730 731 let oldVal = undefined; 732 if (prefExists) { 733 if (ps.type == PREF_BOOLEAN) { 734 try { 735 oldVal = prefs.getBoolPref(ps.name); 736 } catch (e) { 737 badPref = "boolean preference '" + ps.name + "'"; 738 throw "bad pref"; 739 } 740 } else if (ps.type == PREF_STRING) { 741 try { 742 oldVal = prefs.getStringPref(ps.name); 743 } catch (e) { 744 badPref = "string preference '" + ps.name + "'"; 745 throw "bad pref"; 746 } 747 } else if (ps.type == PREF_INTEGER) { 748 try { 749 oldVal = prefs.getIntPref(ps.name); 750 } catch (e) { 751 badPref = "integer preference '" + ps.name + "'"; 752 throw "bad pref"; 753 } 754 } else { 755 throw "internal error - unknown preference type"; 756 } 757 } 758 if (!prefExists || oldVal != ps.value) { 759 g.prefsToRestore.push( { name: ps.name, 760 type: ps.type, 761 value: oldVal, 762 prefExisted: prefExists } ); 763 var value = ps.value; 764 if (ps.type == PREF_BOOLEAN) { 765 prefs.setBoolPref(ps.name, value); 766 } else if (ps.type == PREF_STRING) { 767 prefs.setStringPref(ps.name, value); 768 value = '"' + value + '"'; 769 } else if (ps.type == PREF_INTEGER) { 770 prefs.setIntPref(ps.name, value); 771 } 772 logger.info("SET PREFERENCE pref(" + ps.name + "," + value + ")"); 773 } 774 }); 775 } catch (e) { 776 if (e == "bad pref") { 777 var test = g.urls[0]; 778 if (test.expected == EXPECTED_FAIL) { 779 logger.testEnd(test.identifier, "FAIL", "FAIL", 780 "(SKIPPED; " + badPref + " not known or wrong type)"); 781 ++g.testResults.Skip; 782 } else { 783 logger.testEnd(test.identifier, "FAIL", "PASS", 784 badPref + " not known or wrong type"); 785 ++g.testResults.UnexpectedFail; 786 } 787 788 // skip the test that had a bad preference 789 g.urls.shift(); 790 StartCurrentTest(); 791 return; 792 } else { 793 throw e; 794 } 795 } 796 } 797 798 if (prefSettings.length == 0 && 799 g.uriCanvases[g.currentURL] && 800 (g.urls[0].type == TYPE_REFTEST_EQUAL || 801 g.urls[0].type == TYPE_REFTEST_NOTEQUAL) && 802 g.urls[0].maxAsserts == 0) { 803 // Pretend the document loaded --- RecordResult will notice 804 // there's already a canvas for this URL 805 g.containingWindow.setTimeout(RecordResult, 0); 806 } else { 807 var currentTest = g.totalTests - g.urls.length; 808 // Log this to preserve the same overall log format, 809 // should be removed if the format is updated 810 gDumpFn("REFTEST TEST-LOAD | " + g.currentURL + " | " + currentTest + " / " + g.totalTests + 811 " (" + Math.floor(100 * (currentTest / g.totalTests)) + "%)\n"); 812 TestBuffer("START " + g.currentURL); 813 await updateBrowserRemotenessByURL(g.browser, g.currentURL); 814 815 var type = g.urls[0].type 816 if (TYPE_SCRIPT == type) { 817 SendLoadScriptTest(g.currentURL, g.loadTimeout); 818 } else if (TYPE_PRINT == type) { 819 SendLoadPrintTest(g.currentURL, g.loadTimeout); 820 } else { 821 SendLoadTest(type, g.currentURL, g.currentURLTargetType, g.loadTimeout); 822 } 823 } 824} 825 826function DoneTests() 827{ 828 PerTestCoverageUtils.afterTest() 829 .catch(e => logger.error("EXCEPTION: " + e)) 830 .then(() => { 831 if (g.manageSuite) { 832 g.suiteStarted = false 833 logger.suiteEnd({'results': g.testResults}); 834 } else { 835 logger._logData('results', {results: g.testResults}); 836 } 837 logger.info("Slowest test took " + g.slowestTestTime + "ms (" + g.slowestTestURL + ")"); 838 logger.info("Total canvas count = " + g.recycledCanvases.length); 839 if (g.failedUseWidgetLayers) { 840 LogWidgetLayersFailure(); 841 } 842 843 function onStopped() { 844 if (g.logFile) { 845 g.logFile.close(); 846 g.logFile = null; 847 } 848 let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup); 849 appStartup.quit(Ci.nsIAppStartup.eForceQuit); 850 } 851 if (g.server) { 852 g.server.stop(onStopped); 853 } 854 else { 855 onStopped(); 856 } 857 }); 858} 859 860function UpdateCanvasCache(url, canvas) 861{ 862 var spec = url.spec; 863 864 --g.uriUseCounts[spec]; 865 866 if (g.uriUseCounts[spec] == 0) { 867 ReleaseCanvas(canvas); 868 delete g.uriCanvases[spec]; 869 } else if (g.uriUseCounts[spec] > 0) { 870 g.uriCanvases[spec] = canvas; 871 } else { 872 throw "Use counts were computed incorrectly"; 873 } 874} 875 876// Recompute drawWindow flags for every drawWindow operation. 877// We have to do this every time since our window can be 878// asynchronously resized (e.g. by the window manager, to make 879// it fit on screen) at unpredictable times. 880// Fortunately this is pretty cheap. 881async function DoDrawWindow(ctx, x, y, w, h) 882{ 883 if (g.useDrawSnapshot) { 884 let image = await g.browser.drawSnapshot(x, y, w, h, 1.0, "#fff"); 885 ctx.drawImage(image, x, y); 886 return; 887 } 888 889 var flags = ctx.DRAWWINDOW_DRAW_CARET | ctx.DRAWWINDOW_DRAW_VIEW; 890 var testRect = g.browser.getBoundingClientRect(); 891 if (g.ignoreWindowSize || 892 (0 <= testRect.left && 893 0 <= testRect.top && 894 g.containingWindow.innerWidth >= testRect.right && 895 g.containingWindow.innerHeight >= testRect.bottom)) { 896 // We can use the window's retained layer manager 897 // because the window is big enough to display the entire 898 // browser element 899 flags |= ctx.DRAWWINDOW_USE_WIDGET_LAYERS; 900 } else if (g.browserIsRemote) { 901 logger.error(g.currentURL + " | can't drawWindow remote content"); 902 ++g.testResults.Exception; 903 } 904 905 if (g.drawWindowFlags != flags) { 906 // Every time the flags change, dump the new state. 907 g.drawWindowFlags = flags; 908 var flagsStr = "DRAWWINDOW_DRAW_CARET | DRAWWINDOW_DRAW_VIEW"; 909 if (flags & ctx.DRAWWINDOW_USE_WIDGET_LAYERS) { 910 flagsStr += " | DRAWWINDOW_USE_WIDGET_LAYERS"; 911 } else { 912 // Output a special warning because we need to be able to detect 913 // this whenever it happens. 914 LogWidgetLayersFailure(); 915 g.failedUseWidgetLayers = true; 916 } 917 logger.info("drawWindow flags = " + flagsStr + 918 "; window size = " + g.containingWindow.innerWidth + "," + g.containingWindow.innerHeight + 919 "; test browser size = " + testRect.width + "," + testRect.height); 920 } 921 922 TestBuffer("DoDrawWindow " + x + "," + y + "," + w + "," + h); 923 ctx.save(); 924 ctx.translate(x, y); 925 ctx.drawWindow(g.containingWindow, x, y, w, h, "rgb(255,255,255)", 926 g.drawWindowFlags); 927 ctx.restore(); 928} 929 930async function InitCurrentCanvasWithSnapshot() 931{ 932 TestBuffer("Initializing canvas snapshot"); 933 934 if (g.urls[0].type == TYPE_LOAD || g.urls[0].type == TYPE_SCRIPT || g.urls[0].type == TYPE_PRINT) { 935 // We don't want to snapshot this kind of test 936 return false; 937 } 938 939 if (!g.currentCanvas) { 940 g.currentCanvas = AllocateCanvas(); 941 } 942 943 var ctx = g.currentCanvas.getContext("2d"); 944 await DoDrawWindow(ctx, 0, 0, g.currentCanvas.width, g.currentCanvas.height); 945 return true; 946} 947 948async function UpdateCurrentCanvasForInvalidation(rects) 949{ 950 TestBuffer("Updating canvas for invalidation"); 951 952 if (!g.currentCanvas) { 953 return; 954 } 955 956 var ctx = g.currentCanvas.getContext("2d"); 957 for (var i = 0; i < rects.length; ++i) { 958 var r = rects[i]; 959 // Set left/top/right/bottom to pixel boundaries 960 var left = Math.floor(r.left); 961 var top = Math.floor(r.top); 962 var right = Math.ceil(r.right); 963 var bottom = Math.ceil(r.bottom); 964 965 // Clamp the values to the canvas size 966 left = Math.max(0, Math.min(left, g.currentCanvas.width)); 967 top = Math.max(0, Math.min(top, g.currentCanvas.height)); 968 right = Math.max(0, Math.min(right, g.currentCanvas.width)); 969 bottom = Math.max(0, Math.min(bottom, g.currentCanvas.height)); 970 971 await DoDrawWindow(ctx, left, top, right - left, bottom - top); 972 } 973} 974 975async function UpdateWholeCurrentCanvasForInvalidation() 976{ 977 TestBuffer("Updating entire canvas for invalidation"); 978 979 if (!g.currentCanvas) { 980 return; 981 } 982 983 var ctx = g.currentCanvas.getContext("2d"); 984 await DoDrawWindow(ctx, 0, 0, g.currentCanvas.width, g.currentCanvas.height); 985} 986 987function RecordResult(testRunTime, errorMsg, typeSpecificResults) 988{ 989 TestBuffer("RecordResult fired"); 990 991 // Keep track of which test was slowest, and how long it took. 992 if (testRunTime > g.slowestTestTime) { 993 g.slowestTestTime = testRunTime; 994 g.slowestTestURL = g.currentURL; 995 } 996 997 // Not 'const ...' because of 'EXPECTED_*' value dependency. 998 var outputs = {}; 999 outputs[EXPECTED_PASS] = { 1000 true: {s: ["PASS", "PASS"], n: "Pass"}, 1001 false: {s: ["FAIL", "PASS"], n: "UnexpectedFail"} 1002 }; 1003 outputs[EXPECTED_FAIL] = { 1004 true: {s: ["PASS", "FAIL"], n: "UnexpectedPass"}, 1005 false: {s: ["FAIL", "FAIL"], n: "KnownFail"} 1006 }; 1007 outputs[EXPECTED_RANDOM] = { 1008 true: {s: ["PASS", "PASS"], n: "Random"}, 1009 false: {s: ["FAIL", "FAIL"], n: "Random"} 1010 }; 1011 // for EXPECTED_FUZZY we need special handling because we can have 1012 // Pass, UnexpectedPass, or UnexpectedFail 1013 1014 if ((g.currentURLTargetType == URL_TARGET_TYPE_TEST && g.urls[0].wrCapture.test) || 1015 (g.currentURLTargetType == URL_TARGET_TYPE_REFERENCE && g.urls[0].wrCapture.ref)) { 1016 logger.info("Running webrender capture"); 1017 g.windowUtils.wrCapture(); 1018 } 1019 1020 var output; 1021 var extra; 1022 1023 if (g.urls[0].type == TYPE_LOAD) { 1024 ++g.testResults.LoadOnly; 1025 logger.testStatus(g.urls[0].identifier, "(LOAD ONLY)", "PASS", "PASS"); 1026 g.currentCanvas = null; 1027 FinishTestItem(); 1028 return; 1029 } 1030 if (g.urls[0].type == TYPE_PRINT) { 1031 switch (g.currentURLTargetType) { 1032 case URL_TARGET_TYPE_TEST: 1033 // First document has been loaded. 1034 g.testPrintOutput = typeSpecificResults; 1035 // Proceed to load the second document. 1036 CleanUpCrashDumpFiles(); 1037 StartCurrentURI(URL_TARGET_TYPE_REFERENCE); 1038 break; 1039 case URL_TARGET_TYPE_REFERENCE: 1040 let pathToTestPdf = g.testPrintOutput; 1041 let pathToRefPdf = typeSpecificResults; 1042 comparePdfs(pathToTestPdf, pathToRefPdf, function(error, results) { 1043 let expected = g.urls[0].expected; 1044 // TODO: We should complain here if results is empty! 1045 // (If it's empty, we'll spuriously succeed, regardless of 1046 // our expectations) 1047 if (error) { 1048 output = outputs[expected][false]; 1049 extra = { status_msg: output.n }; 1050 ++g.testResults[output.n]; 1051 logger.testEnd(g.urls[0].identifier, output.s[0], output.s[1], 1052 error.message, null, extra); 1053 } else { 1054 let outputPair = outputs[expected]; 1055 if (expected === EXPECTED_FAIL) { 1056 let failureResults = results.filter(function (result) { return !result.passed }); 1057 if (failureResults.length > 0) { 1058 // We got an expected failure. Let's get rid of the 1059 // passes from the results so we don't trigger 1060 // TEST_UNEXPECTED_PASS logging for those. 1061 results = failureResults; 1062 } 1063 // (else, we expected a failure but got none! 1064 // Leave results untouched so we can log them.) 1065 } 1066 results.forEach(function(result) { 1067 output = outputPair[result.passed]; 1068 let extra = { status_msg: output.n }; 1069 ++g.testResults[output.n]; 1070 logger.testEnd(g.urls[0].identifier, output.s[0], output.s[1], 1071 result.description, null, extra); 1072 }); 1073 } 1074 FinishTestItem(); 1075 }); 1076 break; 1077 default: 1078 throw "Unexpected state."; 1079 } 1080 return; 1081 } 1082 if (g.urls[0].type == TYPE_SCRIPT) { 1083 var expected = g.urls[0].expected; 1084 1085 if (errorMsg) { 1086 // Force an unexpected failure to alert the test author to fix the test. 1087 expected = EXPECTED_PASS; 1088 } else if (typeSpecificResults.length == 0) { 1089 // This failure may be due to a JavaScript Engine bug causing 1090 // early termination of the test. If we do not allow silent 1091 // failure, report an error. 1092 if (!g.urls[0].allowSilentFail) 1093 errorMsg = "No test results reported. (SCRIPT)\n"; 1094 else 1095 logger.info("An expected silent failure occurred"); 1096 } 1097 1098 if (errorMsg) { 1099 output = outputs[expected][false]; 1100 extra = { status_msg: output.n }; 1101 ++g.testResults[output.n]; 1102 logger.testStatus(g.urls[0].identifier, errorMsg, output.s[0], output.s[1], null, null, extra); 1103 FinishTestItem(); 1104 return; 1105 } 1106 1107 var anyFailed = typeSpecificResults.some(function(result) { return !result.passed; }); 1108 var outputPair; 1109 if (anyFailed && expected == EXPECTED_FAIL) { 1110 // If we're marked as expected to fail, and some (but not all) tests 1111 // passed, treat those tests as though they were marked random 1112 // (since we can't tell whether they were really intended to be 1113 // marked failing or not). 1114 outputPair = { true: outputs[EXPECTED_RANDOM][true], 1115 false: outputs[expected][false] }; 1116 } else { 1117 outputPair = outputs[expected]; 1118 } 1119 var index = 0; 1120 typeSpecificResults.forEach(function(result) { 1121 var output = outputPair[result.passed]; 1122 var extra = { status_msg: output.n }; 1123 1124 ++g.testResults[output.n]; 1125 logger.testStatus(g.urls[0].identifier, result.description + " item " + (++index), 1126 output.s[0], output.s[1], null, null, extra); 1127 }); 1128 1129 if (anyFailed && expected == EXPECTED_PASS) { 1130 FlushTestBuffer(); 1131 } 1132 1133 FinishTestItem(); 1134 return; 1135 } 1136 1137 const isRecordingRef = 1138 (g.currentURLTargetType == URL_TARGET_TYPE_REFERENCE); 1139 const prefSettings = 1140 g.urls[0][isRecordingRef ? "prefSettings2" : "prefSettings1"]; 1141 1142 if (prefSettings.length == 0 && g.uriCanvases[g.currentURL]) { 1143 g.currentCanvas = g.uriCanvases[g.currentURL]; 1144 } 1145 if (g.currentCanvas == null) { 1146 logger.error(g.currentURL, "program error managing snapshots"); 1147 ++g.testResults.Exception; 1148 } 1149 g[isRecordingRef ? "canvas2" : "canvas1"] = g.currentCanvas; 1150 g.currentCanvas = null; 1151 1152 ResetRenderingState(); 1153 1154 switch (g.currentURLTargetType) { 1155 case URL_TARGET_TYPE_TEST: 1156 // First document has been loaded. 1157 // Proceed to load the second document. 1158 1159 CleanUpCrashDumpFiles(); 1160 StartCurrentURI(URL_TARGET_TYPE_REFERENCE); 1161 break; 1162 case URL_TARGET_TYPE_REFERENCE: 1163 // Both documents have been loaded. Compare the renderings and see 1164 // if the comparison result matches the expected result specified 1165 // in the manifest. 1166 1167 // number of different pixels 1168 var differences; 1169 // whether the two renderings match: 1170 var equal; 1171 var maxDifference = {}; 1172 // whether the allowed fuzziness from the annotations is exceeded 1173 // by the actual comparison results 1174 var fuzz_exceeded = false; 1175 1176 // what is expected on this platform (PASS, FAIL, RANDOM, or FUZZY) 1177 var expected = g.urls[0].expected; 1178 1179 differences = g.windowUtils.compareCanvases(g.canvas1, g.canvas2, maxDifference); 1180 1181 if (g.urls[0].noAutoFuzz) { 1182 // Autofuzzing is disabled 1183 } else if (isWebRenderOnAndroidDevice() && maxDifference.value <= 2 && differences > 0) { 1184 // Autofuzz for WR on Android physical devices: Reduce any 1185 // maxDifference of 2 to 0, because we get a lot of off-by-ones 1186 // and off-by-twos that are very random and hard to annotate. 1187 // In cases where the difference on any pixel component is more 1188 // than 2 we require manual annotation. Note that this applies 1189 // to both == tests and != tests, so != tests don't 1190 // inadvertently pass due to a random off-by-one pixel 1191 // difference. 1192 logger.info(`REFTEST wr-on-android dropping fuzz of (${maxDifference.value}, ${differences}) to (0, 0)`); 1193 maxDifference.value = 0; 1194 differences = 0; 1195 } 1196 1197 equal = (differences == 0); 1198 1199 if (maxDifference.value > 0 && equal) { 1200 throw "Inconsistent result from compareCanvases."; 1201 } 1202 1203 if (expected == EXPECTED_FUZZY) { 1204 logger.info(`REFTEST fuzzy test ` + 1205 `(${g.urls[0].fuzzyMinDelta}, ${g.urls[0].fuzzyMinPixels}) <= ` + 1206 `(${maxDifference.value}, ${differences}) <= ` + 1207 `(${g.urls[0].fuzzyMaxDelta}, ${g.urls[0].fuzzyMaxPixels})`); 1208 fuzz_exceeded = maxDifference.value > g.urls[0].fuzzyMaxDelta || 1209 differences > g.urls[0].fuzzyMaxPixels; 1210 equal = !fuzz_exceeded && 1211 maxDifference.value >= g.urls[0].fuzzyMinDelta && 1212 differences >= g.urls[0].fuzzyMinPixels; 1213 } 1214 1215 var failedExtraCheck = g.failedNoPaint || g.failedNoDisplayList || g.failedDisplayList || g.failedOpaqueLayer || g.failedAssignedLayer; 1216 1217 // whether the comparison result matches what is in the manifest 1218 var test_passed = (equal == (g.urls[0].type == TYPE_REFTEST_EQUAL)) && !failedExtraCheck; 1219 1220 if (expected != EXPECTED_FUZZY) { 1221 output = outputs[expected][test_passed]; 1222 } else if (test_passed) { 1223 output = {s: ["PASS", "PASS"], n: "Pass"}; 1224 } else if (g.urls[0].type == TYPE_REFTEST_EQUAL && 1225 !failedExtraCheck && 1226 !fuzz_exceeded) { 1227 // If we get here, that means we had an '==' type test where 1228 // at least one of the actual difference values was below the 1229 // allowed range, but nothing else was wrong. So let's produce 1230 // UNEXPECTED-PASS in this scenario. Also, if we enter this 1231 // branch, 'equal' must be false so let's assert that to guard 1232 // against logic errors. 1233 if (equal) { 1234 throw "Logic error in reftest.jsm fuzzy test handling!"; 1235 } 1236 output = {s: ["PASS", "FAIL"], n: "UnexpectedPass"}; 1237 } else { 1238 // In all other cases we fail the test 1239 output = {s: ["FAIL", "PASS"], n: "UnexpectedFail"}; 1240 } 1241 extra = { status_msg: output.n }; 1242 1243 ++g.testResults[output.n]; 1244 1245 // It's possible that we failed both an "extra check" and the normal comparison, but we don't 1246 // have a way to annotate these separately, so just print an error for the extra check failures. 1247 if (failedExtraCheck) { 1248 var failures = []; 1249 if (g.failedNoPaint) { 1250 failures.push("failed reftest-no-paint"); 1251 } 1252 if (g.failedNoDisplayList) { 1253 failures.push("failed reftest-no-display-list"); 1254 } 1255 if (g.failedDisplayList) { 1256 failures.push("failed reftest-display-list"); 1257 } 1258 // The g.failed*Messages arrays will contain messages from both the test and the reference. 1259 if (g.failedOpaqueLayer) { 1260 failures.push("failed reftest-opaque-layer: " + g.failedOpaqueLayerMessages.join(", ")); 1261 } 1262 if (g.failedAssignedLayer) { 1263 failures.push("failed reftest-assigned-layer: " + g.failedAssignedLayerMessages.join(", ")); 1264 } 1265 var failureString = failures.join(", "); 1266 logger.testStatus(g.urls[0].identifier, failureString, output.s[0], output.s[1], null, null, extra); 1267 } else { 1268 var message = "image comparison, max difference: " + maxDifference.value + 1269 ", number of differing pixels: " + differences; 1270 if (!test_passed && expected == EXPECTED_PASS || 1271 !test_passed && expected == EXPECTED_FUZZY || 1272 test_passed && expected == EXPECTED_FAIL) { 1273 if (!equal) { 1274 extra.max_difference = maxDifference.value; 1275 extra.differences = differences; 1276 var image1 = g.canvas1.toDataURL(); 1277 var image2 = g.canvas2.toDataURL(); 1278 extra.reftest_screenshots = [ 1279 {url:g.urls[0].identifier[0], 1280 screenshot: image1.slice(image1.indexOf(",") + 1)}, 1281 g.urls[0].identifier[1], 1282 {url:g.urls[0].identifier[2], 1283 screenshot: image2.slice(image2.indexOf(",") + 1)} 1284 ]; 1285 extra.image1 = image1; 1286 extra.image2 = image2; 1287 } else { 1288 var image1 = g.canvas1.toDataURL(); 1289 extra.reftest_screenshots = [ 1290 {url:g.urls[0].identifier[0], 1291 screenshot: image1.slice(image1.indexOf(",") + 1)} 1292 ]; 1293 extra.image1 = image1; 1294 } 1295 } 1296 logger.testStatus(g.urls[0].identifier, message, output.s[0], output.s[1], null, null, extra); 1297 1298 if (g.noCanvasCache) { 1299 ReleaseCanvas(g.canvas1); 1300 ReleaseCanvas(g.canvas2); 1301 } else { 1302 if (g.urls[0].prefSettings1.length == 0) { 1303 UpdateCanvasCache(g.urls[0].url1, g.canvas1); 1304 } 1305 if (g.urls[0].prefSettings2.length == 0) { 1306 UpdateCanvasCache(g.urls[0].url2, g.canvas2); 1307 } 1308 } 1309 } 1310 1311 if ((!test_passed && expected == EXPECTED_PASS) || (test_passed && expected == EXPECTED_FAIL)) { 1312 FlushTestBuffer(); 1313 } 1314 1315 CleanUpCrashDumpFiles(); 1316 FinishTestItem(); 1317 break; 1318 default: 1319 throw "Unexpected state."; 1320 } 1321} 1322 1323function LoadFailed(why) 1324{ 1325 ++g.testResults.FailedLoad; 1326 if (!why) { 1327 // reftest-content.js sets an initial reason before it sets the 1328 // timeout that will call us with the currently set reason, so we 1329 // should never get here. If we do then there's a logic error 1330 // somewhere. Perhaps tests are somehow running overlapped and the 1331 // timeout for one test is not being cleared before the timeout for 1332 // another is set? Maybe there's some sort of race? 1333 logger.error("load failed with unknown reason (we should always have a reason!)"); 1334 } 1335 logger.testStatus(g.urls[0].identifier, "load failed: " + why, "FAIL", "PASS"); 1336 FlushTestBuffer(); 1337 FinishTestItem(); 1338} 1339 1340function RemoveExpectedCrashDumpFiles() 1341{ 1342 if (g.expectingProcessCrash) { 1343 for (let crashFilename of g.expectedCrashDumpFiles) { 1344 let file = g.crashDumpDir.clone(); 1345 file.append(crashFilename); 1346 if (file.exists()) { 1347 file.remove(false); 1348 } 1349 } 1350 } 1351 g.expectedCrashDumpFiles.length = 0; 1352} 1353 1354function FindUnexpectedCrashDumpFiles() 1355{ 1356 if (!g.crashDumpDir.exists()) { 1357 return; 1358 } 1359 1360 let entries = g.crashDumpDir.directoryEntries; 1361 if (!entries) { 1362 return; 1363 } 1364 1365 let foundCrashDumpFile = false; 1366 while (entries.hasMoreElements()) { 1367 let file = entries.nextFile; 1368 let path = String(file.path); 1369 if (path.match(/\.(dmp|extra)$/) && !g.unexpectedCrashDumpFiles[path]) { 1370 if (!foundCrashDumpFile) { 1371 ++g.testResults.UnexpectedFail; 1372 foundCrashDumpFile = true; 1373 if (g.currentURL) { 1374 logger.testStatus(g.urls[0].identifier, "crash-check", "FAIL", "PASS", "This test left crash dumps behind, but we weren't expecting it to!"); 1375 } else { 1376 logger.error("Harness startup left crash dumps behind, but we weren't expecting it to!"); 1377 } 1378 } 1379 logger.info("Found unexpected crash dump file " + path); 1380 g.unexpectedCrashDumpFiles[path] = true; 1381 } 1382 } 1383} 1384 1385function RemovePendingCrashDumpFiles() 1386{ 1387 if (!g.pendingCrashDumpDir.exists()) { 1388 return; 1389 } 1390 1391 let entries = g.pendingCrashDumpDir.directoryEntries; 1392 while (entries.hasMoreElements()) { 1393 let file = entries.nextFile; 1394 if (file.isFile()) { 1395 file.remove(false); 1396 logger.info("This test left pending crash dumps; deleted "+file.path); 1397 } 1398 } 1399} 1400 1401function CleanUpCrashDumpFiles() 1402{ 1403 RemoveExpectedCrashDumpFiles(); 1404 FindUnexpectedCrashDumpFiles(); 1405 if (g.cleanupPendingCrashes) { 1406 RemovePendingCrashDumpFiles(); 1407 } 1408 g.expectingProcessCrash = false; 1409} 1410 1411function FinishTestItem() 1412{ 1413 logger.testEnd(g.urls[0].identifier, "OK"); 1414 1415 // Replace document with BLANK_URL_FOR_CLEARING in case there are 1416 // assertions when unloading. 1417 logger.debug("Loading a blank page"); 1418 // After clearing, content will notify us of the assertion count 1419 // and tests will continue. 1420 SendClear(); 1421 g.failedNoPaint = false; 1422 g.failedNoDisplayList = false; 1423 g.failedDisplayList = false; 1424 g.failedOpaqueLayer = false; 1425 g.failedOpaqueLayerMessages = []; 1426 g.failedAssignedLayer = false; 1427 g.failedAssignedLayerMessages = []; 1428} 1429 1430function DoAssertionCheck(numAsserts) 1431{ 1432 if (g.debug.isDebugBuild) { 1433 if (g.browserIsRemote) { 1434 // Count chrome-process asserts too when content is out of 1435 // process. 1436 var newAssertionCount = g.debug.assertionCount; 1437 var numLocalAsserts = newAssertionCount - g.assertionCount; 1438 g.assertionCount = newAssertionCount; 1439 1440 numAsserts += numLocalAsserts; 1441 } 1442 1443 var minAsserts = g.urls[0].minAsserts; 1444 var maxAsserts = g.urls[0].maxAsserts; 1445 1446 if (numAsserts < minAsserts) { 1447 ++g.testResults.AssertionUnexpectedFixed; 1448 } else if (numAsserts > maxAsserts) { 1449 ++g.testResults.AssertionUnexpected; 1450 } else if (numAsserts != 0) { 1451 ++g.testResults.AssertionKnown; 1452 } 1453 logger.assertionCount(g.urls[0].identifier, numAsserts, minAsserts, maxAsserts); 1454 } 1455 1456 if (g.urls[0].chaosMode) { 1457 g.windowUtils.leaveChaosMode(); 1458 } 1459 1460 // And start the next test. 1461 g.urls.shift(); 1462 StartCurrentTest(); 1463} 1464 1465function ResetRenderingState() 1466{ 1467 SendResetRenderingState(); 1468 // We would want to clear any viewconfig here, if we add support for it 1469} 1470 1471function RestoreChangedPreferences() 1472{ 1473 if (g.prefsToRestore.length > 0) { 1474 var prefs = Cc["@mozilla.org/preferences-service;1"]. 1475 getService(Ci.nsIPrefBranch); 1476 g.prefsToRestore.reverse(); 1477 g.prefsToRestore.forEach(function(ps) { 1478 if (ps.prefExisted) { 1479 var value = ps.value; 1480 if (ps.type == PREF_BOOLEAN) { 1481 prefs.setBoolPref(ps.name, value); 1482 } else if (ps.type == PREF_STRING) { 1483 prefs.setStringPref(ps.name, value); 1484 value = '"' + value + '"'; 1485 } else if (ps.type == PREF_INTEGER) { 1486 prefs.setIntPref(ps.name, value); 1487 } 1488 logger.info("RESTORE PREFERENCE pref(" + ps.name + "," + value + ")"); 1489 } else { 1490 prefs.clearUserPref(ps.name); 1491 logger.info("RESTORE PREFERENCE pref(" + ps.name + ", <no value set>) (clearing user pref)"); 1492 } 1493 }); 1494 g.prefsToRestore = []; 1495 } 1496} 1497 1498function RegisterMessageListenersAndLoadContentScript(aReload) 1499{ 1500 g.browserMessageManager.addMessageListener( 1501 "reftest:AssertionCount", 1502 function (m) { RecvAssertionCount(m.json.count); } 1503 ); 1504 g.browserMessageManager.addMessageListener( 1505 "reftest:ContentReady", 1506 function (m) { return RecvContentReady(m.data); } 1507 ); 1508 g.browserMessageManager.addMessageListener( 1509 "reftest:Exception", 1510 function (m) { RecvException(m.json.what) } 1511 ); 1512 g.browserMessageManager.addMessageListener( 1513 "reftest:FailedLoad", 1514 function (m) { RecvFailedLoad(m.json.why); } 1515 ); 1516 g.browserMessageManager.addMessageListener( 1517 "reftest:FailedNoPaint", 1518 function (m) { RecvFailedNoPaint(); } 1519 ); 1520 g.browserMessageManager.addMessageListener( 1521 "reftest:FailedNoDisplayList", 1522 function (m) { RecvFailedNoDisplayList(); } 1523 ); 1524 g.browserMessageManager.addMessageListener( 1525 "reftest:FailedDisplayList", 1526 function (m) { RecvFailedDisplayList(); } 1527 ); 1528 g.browserMessageManager.addMessageListener( 1529 "reftest:FailedOpaqueLayer", 1530 function (m) { RecvFailedOpaqueLayer(m.json.why); } 1531 ); 1532 g.browserMessageManager.addMessageListener( 1533 "reftest:FailedAssignedLayer", 1534 function (m) { RecvFailedAssignedLayer(m.json.why); } 1535 ); 1536 g.browserMessageManager.addMessageListener( 1537 "reftest:InitCanvasWithSnapshot", 1538 function (m) { RecvInitCanvasWithSnapshot(); } 1539 ); 1540 g.browserMessageManager.addMessageListener( 1541 "reftest:Log", 1542 function (m) { RecvLog(m.json.type, m.json.msg); } 1543 ); 1544 g.browserMessageManager.addMessageListener( 1545 "reftest:ScriptResults", 1546 function (m) { RecvScriptResults(m.json.runtimeMs, m.json.error, m.json.results); } 1547 ); 1548 g.browserMessageManager.addMessageListener( 1549 "reftest:StartPrint", 1550 function (m) { RecvStartPrint(m.json.isPrintSelection, m.json.printRange); } 1551 ); 1552 g.browserMessageManager.addMessageListener( 1553 "reftest:PrintResult", 1554 function (m) { RecvPrintResult(m.json.runtimeMs, m.json.status, m.json.fileName); } 1555 ); 1556 g.browserMessageManager.addMessageListener( 1557 "reftest:TestDone", 1558 function (m) { RecvTestDone(m.json.runtimeMs); } 1559 ); 1560 g.browserMessageManager.addMessageListener( 1561 "reftest:UpdateCanvasForInvalidation", 1562 function (m) { RecvUpdateCanvasForInvalidation(m.json.rects); } 1563 ); 1564 g.browserMessageManager.addMessageListener( 1565 "reftest:UpdateWholeCanvasForInvalidation", 1566 function (m) { RecvUpdateWholeCanvasForInvalidation(); } 1567 ); 1568 g.browserMessageManager.addMessageListener( 1569 "reftest:ExpectProcessCrash", 1570 function (m) { RecvExpectProcessCrash(); } 1571 ); 1572 1573 g.browserMessageManager.loadFrameScript("resource://reftest/reftest-content.js", true, true); 1574 1575 if (aReload) { 1576 return; 1577 } 1578 1579 ChromeUtils.registerWindowActor("ReftestFission", { 1580 parent: { 1581 moduleURI: "resource://reftest/ReftestFissionParent.jsm", 1582 }, 1583 child: { 1584 moduleURI: "resource://reftest/ReftestFissionChild.jsm", 1585 events: { 1586 MozAfterPaint: {}, 1587 }, 1588 }, 1589 allFrames: true, 1590 includeChrome: true, 1591 }); 1592} 1593 1594function RecvAssertionCount(count) 1595{ 1596 DoAssertionCheck(count); 1597} 1598 1599function RecvContentReady(info) 1600{ 1601 if (g.resolveContentReady) { 1602 g.resolveContentReady(); 1603 g.resolveContentReady = null; 1604 } else { 1605 g.contentGfxInfo = info.gfx; 1606 InitAndStartRefTests(); 1607 } 1608 return { remote: g.browserIsRemote }; 1609} 1610 1611function RecvException(what) 1612{ 1613 logger.error(g.currentURL + " | " + what); 1614 ++g.testResults.Exception; 1615} 1616 1617function RecvFailedLoad(why) 1618{ 1619 LoadFailed(why); 1620} 1621 1622function RecvFailedNoPaint() 1623{ 1624 g.failedNoPaint = true; 1625} 1626 1627function RecvFailedNoDisplayList() 1628{ 1629 g.failedNoDisplayList = true; 1630} 1631 1632function RecvFailedDisplayList() 1633{ 1634 g.failedDisplayList = true; 1635} 1636 1637function RecvFailedOpaqueLayer(why) { 1638 g.failedOpaqueLayer = true; 1639 g.failedOpaqueLayerMessages.push(why); 1640} 1641 1642function RecvFailedAssignedLayer(why) { 1643 g.failedAssignedLayer = true; 1644 g.failedAssignedLayerMessages.push(why); 1645} 1646 1647async function RecvInitCanvasWithSnapshot() 1648{ 1649 var painted = await InitCurrentCanvasWithSnapshot(); 1650 SendUpdateCurrentCanvasWithSnapshotDone(painted); 1651} 1652 1653function RecvLog(type, msg) 1654{ 1655 msg = "[CONTENT] " + msg; 1656 if (type == "info") { 1657 TestBuffer(msg); 1658 } else if (type == "warning") { 1659 logger.warning(msg); 1660 } else if (type == "error") { 1661 logger.error("REFTEST TEST-UNEXPECTED-FAIL | " + g.currentURL + " | " + msg + "\n"); 1662 ++g.testResults.Exception; 1663 } else { 1664 logger.error("REFTEST TEST-UNEXPECTED-FAIL | " + g.currentURL + " | unknown log type " + type + "\n"); 1665 ++g.testResults.Exception; 1666 } 1667} 1668 1669function RecvScriptResults(runtimeMs, error, results) 1670{ 1671 RecordResult(runtimeMs, error, results); 1672} 1673 1674function RecvStartPrint(isPrintSelection, printRange) 1675{ 1676 let fileName =`reftest-print-${Date.now()}-`; 1677 crypto.getRandomValues(new Uint8Array(4)).forEach(x => fileName += x.toString(16)); 1678 fileName += ".pdf" 1679 let file = Services.dirsvc.get("TmpD", Ci.nsIFile); 1680 file.append(fileName); 1681 1682 let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(Ci.nsIPrintSettingsService); 1683 let ps = PSSVC.newPrintSettings; 1684 ps.printSilent = true; 1685 ps.showPrintProgress = false; 1686 ps.printBGImages = true; 1687 ps.printBGColors = true; 1688 ps.unwriteableMarginTop = 0; 1689 ps.unwriteableMarginRight = 0; 1690 ps.unwriteableMarginLeft = 0; 1691 ps.unwriteableMarginBottom = 0; 1692 ps.printToFile = true; 1693 ps.toFileName = file.path; 1694 ps.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF; 1695 ps.printSelectionOnly = isPrintSelection; 1696 if (printRange) { 1697 ps.pageRanges = printRange.split(',').map(function(r) { 1698 let range = r.split('-'); 1699 return [+range[0] || 1, +range[1] || 1] 1700 }).flat(); 1701 } 1702 1703 var prefs = Cc["@mozilla.org/preferences-service;1"]. 1704 getService(Ci.nsIPrefBranch); 1705 ps.printInColor = prefs.getBoolPref("print.print_in_color", true); 1706 1707 g.browser.browsingContext.print(ps) 1708 .then(() => SendPrintDone(Cr.NS_OK, file.path)) 1709 .catch(exception => SendPrintDone(exception.code, file.path)); 1710} 1711 1712function RecvPrintResult(runtimeMs, status, fileName) 1713{ 1714 if (!Components.isSuccessCode(status)) { 1715 logger.error("REFTEST TEST-UNEXPECTED-FAIL | " + g.currentURL + " | error during printing\n"); 1716 ++g.testResults.Exception; 1717 } 1718 RecordResult(runtimeMs, '', fileName); 1719} 1720 1721function RecvTestDone(runtimeMs) 1722{ 1723 RecordResult(runtimeMs, '', [ ]); 1724} 1725 1726async function RecvUpdateCanvasForInvalidation(rects) 1727{ 1728 await UpdateCurrentCanvasForInvalidation(rects); 1729 SendUpdateCurrentCanvasWithSnapshotDone(true); 1730} 1731 1732async function RecvUpdateWholeCanvasForInvalidation() 1733{ 1734 await UpdateWholeCurrentCanvasForInvalidation(); 1735 SendUpdateCurrentCanvasWithSnapshotDone(true); 1736} 1737 1738function OnProcessCrashed(subject, topic, data) 1739{ 1740 let id; 1741 let additionalDumps; 1742 let propbag = subject.QueryInterface(Ci.nsIPropertyBag2); 1743 1744 if (topic == "ipc:content-shutdown") { 1745 id = propbag.get("dumpID"); 1746 } 1747 1748 if (id) { 1749 g.expectedCrashDumpFiles.push(id + ".dmp"); 1750 g.expectedCrashDumpFiles.push(id + ".extra"); 1751 } 1752 1753 if (additionalDumps && additionalDumps.length != 0) { 1754 for (const name of additionalDumps.split(',')) { 1755 g.expectedCrashDumpFiles.push(id + "-" + name + ".dmp"); 1756 } 1757 } 1758} 1759 1760function RegisterProcessCrashObservers() 1761{ 1762 var os = Cc[NS_OBSERVER_SERVICE_CONTRACTID] 1763 .getService(Ci.nsIObserverService); 1764 os.addObserver(OnProcessCrashed, "ipc:content-shutdown"); 1765} 1766 1767function RecvExpectProcessCrash() 1768{ 1769 g.expectingProcessCrash = true; 1770} 1771 1772function SendClear() 1773{ 1774 g.browserMessageManager.sendAsyncMessage("reftest:Clear"); 1775} 1776 1777function SendLoadScriptTest(uri, timeout) 1778{ 1779 g.browserMessageManager.sendAsyncMessage("reftest:LoadScriptTest", 1780 { uri: uri, timeout: timeout }); 1781} 1782 1783function SendLoadPrintTest(uri, timeout) 1784{ 1785 g.browserMessageManager.sendAsyncMessage("reftest:LoadPrintTest", 1786 { uri: uri, timeout: timeout }); 1787} 1788 1789function SendLoadTest(type, uri, uriTargetType, timeout) 1790{ 1791 g.browserMessageManager.sendAsyncMessage("reftest:LoadTest", 1792 { type: type, uri: uri, 1793 uriTargetType: uriTargetType, 1794 timeout: timeout } 1795 ); 1796} 1797 1798function SendResetRenderingState() 1799{ 1800 g.browserMessageManager.sendAsyncMessage("reftest:ResetRenderingState"); 1801} 1802 1803function SendPrintDone(status, fileName) 1804{ 1805 g.browserMessageManager.sendAsyncMessage("reftest:PrintDone", { status, fileName }); 1806} 1807 1808function SendUpdateCurrentCanvasWithSnapshotDone(painted) 1809{ 1810 g.browserMessageManager.sendAsyncMessage("reftest:UpdateCanvasWithSnapshotDone", { painted }); 1811} 1812 1813var pdfjsHasLoaded; 1814 1815function pdfjsHasLoadedPromise() { 1816 if (pdfjsHasLoaded === undefined) { 1817 pdfjsHasLoaded = new Promise((resolve, reject) => { 1818 let doc = g.containingWindow.document; 1819 const script = doc.createElement("script"); 1820 script.src = "resource://pdf.js/build/pdf.js"; 1821 script.onload = resolve; 1822 script.onerror = () => reject(new Error("PDF.js script load failed.")); 1823 doc.documentElement.appendChild(script); 1824 }); 1825 } 1826 1827 return pdfjsHasLoaded; 1828} 1829 1830function readPdf(path, callback) { 1831 OS.File.open(path, { read: true }).then(function (file) { 1832 file.read().then(function (data) { 1833 pdfjsLib.GlobalWorkerOptions.workerSrc = "resource://pdf.js/build/pdf.worker.js"; 1834 pdfjsLib.getDocument({ 1835 data: data 1836 }).promise.then(function (pdf) { 1837 callback(null, pdf); 1838 }, function (e) { 1839 callback(new Error(`Couldn't parse ${path}, exception: ${e}`)); 1840 }); 1841 return; 1842 }, function (e) { 1843 callback(new Error(`Couldn't read PDF ${path}, exception: ${e}`)); 1844 }); 1845 }); 1846} 1847 1848function comparePdfs(pathToTestPdf, pathToRefPdf, callback) { 1849 pdfjsHasLoadedPromise().then(() => 1850 Promise.all([pathToTestPdf, pathToRefPdf].map(function(path) { 1851 return new Promise(function(resolve, reject) { 1852 readPdf(path, function(error, pdf) { 1853 // Resolve or reject outer promise. reject and resolve are 1854 // passed to the callback function given as first arguments 1855 // to the Promise constructor. 1856 if (error) { 1857 reject(error); 1858 } else { 1859 resolve(pdf); 1860 } 1861 }); 1862 }); 1863 }))).then(function(pdfs) { 1864 let numberOfPages = pdfs[1].numPages; 1865 let sameNumberOfPages = numberOfPages === pdfs[0].numPages; 1866 1867 let resultPromises = [Promise.resolve({ 1868 passed: sameNumberOfPages, 1869 description: "Expected number of pages: " + numberOfPages + 1870 ", got " + pdfs[0].numPages 1871 })]; 1872 1873 if (sameNumberOfPages) { 1874 for (let i = 0; i < numberOfPages; i++) { 1875 let pageNum = i + 1; 1876 let testPagePromise = pdfs[0].getPage(pageNum); 1877 let refPagePromise = pdfs[1].getPage(pageNum); 1878 resultPromises.push(new Promise(function(resolve, reject) { 1879 Promise.all([testPagePromise, refPagePromise]).then(function(pages) { 1880 let testTextPromise = pages[0].getTextContent(); 1881 let refTextPromise = pages[1].getTextContent(); 1882 Promise.all([testTextPromise, refTextPromise]).then(function(texts) { 1883 let testTextItems = texts[0].items; 1884 let refTextItems = texts[1].items; 1885 let testText; 1886 let refText; 1887 let passed = refTextItems.every(function(o, i) { 1888 refText = o.str; 1889 if (!testTextItems[i]) { 1890 return false; 1891 } 1892 testText = testTextItems[i].str; 1893 return testText === refText; 1894 }); 1895 let description; 1896 if (passed) { 1897 if (testTextItems.length > refTextItems.length) { 1898 passed = false; 1899 description = "Page " + pages[0].pageNumber + 1900 " contains unexpected text like '" + 1901 testTextItems[refTextItems.length].str + "'"; 1902 } else { 1903 description = "Page " + pages[0].pageNumber + 1904 " contains same text" 1905 } 1906 } else { 1907 description = "Expected page " + pages[0].pageNumber + 1908 " to contain text '" + refText; 1909 if (testText) { 1910 description += "' but found '" + testText + 1911 "' instead"; 1912 } 1913 } 1914 resolve({ 1915 passed: passed, 1916 description: description 1917 }); 1918 }, reject); 1919 }, reject); 1920 })); 1921 } 1922 } 1923 1924 Promise.all(resultPromises).then(function (results) { 1925 callback(null, results); 1926 }); 1927 }, function(error) { 1928 callback(error); 1929 }); 1930} 1931