reftest-content.js (53583B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /* eslint-env mozilla/frame-script */ 6 7 const XHTML_NS = "http://www.w3.org/1999/xhtml"; 8 9 const DEBUG_CONTRACTID = "@mozilla.org/xpcom/debug;1"; 10 const PRINTSETTINGS_CONTRACTID = "@mozilla.org/gfx/printsettings-service;1"; 11 const NS_OBSERVER_SERVICE_CONTRACTID = "@mozilla.org/observer-service;1"; 12 const NS_GFXINFO_CONTRACTID = "@mozilla.org/gfx/info;1"; 13 const IO_SERVICE_CONTRACTID = "@mozilla.org/network/io-service;1"; 14 15 // "<!--CLEAR-->" 16 const BLANK_URL_FOR_CLEARING = 17 "data:text/html;charset=UTF-8,%3C%21%2D%2DCLEAR%2D%2D%3E"; 18 19 const { setTimeout, clearTimeout } = ChromeUtils.importESModule( 20 "resource://gre/modules/Timer.sys.mjs" 21 ); 22 const { onSpellCheck } = ChromeUtils.importESModule( 23 "resource://reftest/AsyncSpellCheckTestHelper.sys.mjs" 24 ); 25 26 // This will load chrome Custom Elements inside chrome documents: 27 ChromeUtils.importESModule( 28 "resource://gre/modules/CustomElementsListener.sys.mjs" 29 ); 30 31 var gBrowserIsRemote; 32 var gHaveCanvasSnapshot = false; 33 var gCurrentURL; 34 var gCurrentURLRecordResults; 35 var gCurrentURLTargetType; 36 var gCurrentTestType; 37 var gTimeoutHook = null; 38 var gFailureTimeout = null; 39 var gFailureReason; 40 var gAssertionCount = 0; 41 var gUpdateCanvasPromiseResolver = null; 42 43 var gDebug; 44 var gVerbose = false; 45 46 var gCurrentTestStartTime; 47 var gClearingForAssertionCheck = false; 48 49 const TYPE_LOAD = "load"; // test without a reference (just test that it does 50 // not assert, crash, hang, or leak) 51 const TYPE_SCRIPT = "script"; // test contains individual test results 52 const TYPE_PRINT = "print"; // test and reference will be printed to PDF's and 53 // compared structurally 54 55 // keep this in sync with globals.sys.mjs 56 const URL_TARGET_TYPE_TEST = 0; // first url 57 const URL_TARGET_TYPE_REFERENCE = 1; // second url, if any 58 59 function webNavigation() { 60 return docShell.QueryInterface(Ci.nsIWebNavigation); 61 } 62 63 function webProgress() { 64 return docShell 65 .QueryInterface(Ci.nsIInterfaceRequestor) 66 .getInterface(Ci.nsIWebProgress); 67 } 68 69 function windowUtilsForWindow(w) { 70 return w.windowUtils; 71 } 72 73 function windowUtils() { 74 return windowUtilsForWindow(content); 75 } 76 77 function IDForEventTarget(event) { 78 try { 79 return "'" + event.target.getAttribute("id") + "'"; 80 } catch (ex) { 81 return "<unknown>"; 82 } 83 } 84 85 var progressListener = { 86 onStateChange(webprogress, request, flags) { 87 let uri; 88 try { 89 request.QueryInterface(Ci.nsIChannel); 90 uri = request.originalURI.spec; 91 } catch (ex) { 92 return; 93 } 94 const WPL = Ci.nsIWebProgressListener; 95 const endFlags = 96 WPL.STATE_STOP | WPL.STATE_IS_WINDOW | WPL.STATE_IS_NETWORK; 97 if ((flags & endFlags) == endFlags) { 98 OnDocumentLoad(uri); 99 } 100 }, 101 QueryInterface: ChromeUtils.generateQI([ 102 "nsIWebProgressListener", 103 "nsISupportsWeakReference", 104 ]), 105 }; 106 107 function OnInitialLoad() { 108 removeEventListener("load", OnInitialLoad, true); 109 110 gDebug = Cc[DEBUG_CONTRACTID].getService(Ci.nsIDebug2); 111 if (gDebug.isDebugBuild) { 112 gAssertionCount = gDebug.assertionCount; 113 } 114 gVerbose = !!Services.env.get("MOZ_REFTEST_VERBOSE"); 115 116 RegisterMessageListeners(); 117 118 var initInfo = SendContentReady(); 119 gBrowserIsRemote = initInfo.remote; 120 121 webProgress().addProgressListener( 122 progressListener, 123 Ci.nsIWebProgress.NOTIFY_STATE_WINDOW 124 ); 125 126 LogInfo("Using browser remote=" + gBrowserIsRemote + "\n"); 127 } 128 129 function SetFailureTimeout(cb, timeout, uri) { 130 var targetTime = Date.now() + timeout; 131 132 var wrapper = function () { 133 // Timeouts can fire prematurely in some cases (e.g. in chaos mode). If this 134 // happens, set another timeout for the remaining time. 135 let remainingMs = targetTime - Date.now(); 136 if (remainingMs > 0) { 137 SetFailureTimeout(cb, remainingMs); 138 } else { 139 cb(); 140 } 141 }; 142 143 // Once OnDocumentLoad is called to handle the 'load' event it will update 144 // this error message to reflect what stage of the processing it has reached 145 // as it advances to each stage in turn. 146 gFailureReason = 147 "timed out after " + timeout + " ms waiting for 'load' event for " + uri; 148 gFailureTimeout = setTimeout(wrapper, timeout); 149 } 150 151 function StartTestURI(type, uri, uriTargetType, timeout) { 152 // The GC is only able to clean up compartments after the CC runs. Since 153 // the JS ref tests disable the normal browser chrome and do not otherwise 154 // create substatial DOM garbage, the CC tends not to run enough normally. 155 windowUtils().runNextCollectorTimer(); 156 157 gCurrentTestType = type; 158 gCurrentURL = uri; 159 gCurrentURLTargetType = uriTargetType; 160 gCurrentURLRecordResults = 0; 161 162 gCurrentTestStartTime = Date.now(); 163 if (gFailureTimeout != null) { 164 SendException("program error managing timeouts\n"); 165 } 166 SetFailureTimeout(LoadFailed, timeout, uri); 167 168 LoadURI(gCurrentURL); 169 } 170 171 function setupTextZoom(contentRootElement) { 172 if ( 173 !contentRootElement || 174 !contentRootElement.hasAttribute("reftest-text-zoom") 175 ) { 176 return; 177 } 178 docShell.browsingContext.textZoom = 179 contentRootElement.getAttribute("reftest-text-zoom"); 180 } 181 182 function setupFullZoom(contentRootElement) { 183 if (!contentRootElement || !contentRootElement.hasAttribute("reftest-zoom")) { 184 return; 185 } 186 docShell.browsingContext.fullZoom = 187 contentRootElement.getAttribute("reftest-zoom"); 188 } 189 190 function resetZoomAndTextZoom() { 191 docShell.browsingContext.fullZoom = 1.0; 192 docShell.browsingContext.textZoom = 1.0; 193 } 194 195 function doPrintMode(contentRootElement) { 196 // use getAttribute because className works differently in HTML and SVG 197 if (contentRootElement && contentRootElement.hasAttribute("class")) { 198 var classList = contentRootElement.getAttribute("class").split(/\s+/); 199 if (classList.includes("reftest-print")) { 200 SendException("reftest-print is obsolete, use reftest-paged instead"); 201 return false; 202 } 203 return classList.includes("reftest-paged"); 204 } 205 return false; 206 } 207 208 function setupPrintMode(contentRootElement) { 209 var PSSVC = Cc[PRINTSETTINGS_CONTRACTID].getService( 210 Ci.nsIPrintSettingsService 211 ); 212 var ps = PSSVC.createNewPrintSettings(); 213 ps.paperWidth = 5; 214 ps.paperHeight = 3; 215 216 // Override any os-specific unwriteable margins 217 ps.unwriteableMarginTop = 0; 218 ps.unwriteableMarginLeft = 0; 219 ps.unwriteableMarginBottom = 0; 220 ps.unwriteableMarginRight = 0; 221 222 ps.headerStrLeft = ""; 223 ps.headerStrCenter = ""; 224 ps.headerStrRight = ""; 225 ps.footerStrLeft = ""; 226 ps.footerStrCenter = ""; 227 ps.footerStrRight = ""; 228 229 const printBackgrounds = (() => { 230 const attr = contentRootElement.getAttribute("reftest-paged-backgrounds"); 231 return !attr || attr != "false"; 232 })(); 233 ps.printBGColors = printBackgrounds; 234 ps.printBGImages = printBackgrounds; 235 236 docShell.docViewer.setPageModeForTesting(/* aPageMode */ true, ps); 237 } 238 239 // Message the parent process to ask it to print the current page to a PDF file. 240 function printToPdf() { 241 let currentDoc = content.document; 242 let isPrintSelection = false; 243 let printRange = ""; 244 245 if (currentDoc) { 246 let contentRootElement = currentDoc.documentElement; 247 printRange = contentRootElement.getAttribute("reftest-print-range") || ""; 248 } 249 250 if (printRange) { 251 if (printRange === "selection") { 252 isPrintSelection = true; 253 } else if ( 254 !printRange.split(",").every(range => /^[1-9]\d*-[1-9]\d*$/.test(range)) 255 ) { 256 SendException("invalid value for reftest-print-range"); 257 return; 258 } 259 } 260 261 SendStartPrint(isPrintSelection, printRange); 262 } 263 264 function attrOrDefault(element, attr, def) { 265 return element.hasAttribute(attr) ? Number(element.getAttribute(attr)) : def; 266 } 267 268 function setupViewport(contentRootElement) { 269 if (!contentRootElement) { 270 return; 271 } 272 273 var sw = attrOrDefault(contentRootElement, "reftest-scrollport-w", 0); 274 var sh = attrOrDefault(contentRootElement, "reftest-scrollport-h", 0); 275 if (sw !== 0 || sh !== 0) { 276 LogInfo("Setting viewport to <w=" + sw + ", h=" + sh + ">"); 277 windowUtils().setVisualViewportSize(sw, sh); 278 } 279 280 var res = attrOrDefault(contentRootElement, "reftest-resolution", 1); 281 if (res !== 1) { 282 LogInfo("Setting resolution to " + res); 283 windowUtils().setResolutionAndScaleTo(res); 284 } 285 286 // XXX support viewconfig when needed 287 } 288 289 function setupDisplayport() { 290 let promise = content.windowGlobalChild 291 .getActor("ReftestFission") 292 .SetupDisplayportRoot(); 293 return promise.then( 294 function (result) { 295 for (let errorString of result.errorStrings) { 296 LogError(errorString); 297 } 298 for (let infoString of result.infoStrings) { 299 LogInfo(infoString); 300 } 301 }, 302 function (reason) { 303 LogError("SetupDisplayportRoot returned promise rejected: " + reason); 304 } 305 ); 306 } 307 308 // Returns whether any offsets were updated 309 function setupAsyncScrollOffsets(options) { 310 let currentDoc = content.document; 311 let contentRootElement = currentDoc ? currentDoc.documentElement : null; 312 313 if ( 314 !contentRootElement || 315 !contentRootElement.hasAttribute("reftest-async-scroll") 316 ) { 317 return Promise.resolve(false); 318 } 319 320 let allowFailure = options.allowFailure; 321 let promise = content.windowGlobalChild 322 .getActor("ReftestFission") 323 .sendQuery("SetupAsyncScrollOffsets", { allowFailure }); 324 return promise.then( 325 function (result) { 326 for (let errorString of result.errorStrings) { 327 LogError(errorString); 328 } 329 for (let infoString of result.infoStrings) { 330 LogInfo(infoString); 331 } 332 return result.updatedAny; 333 }, 334 function (reason) { 335 LogError( 336 "SetupAsyncScrollOffsets SendQuery to parent promise rejected: " + 337 reason 338 ); 339 return false; 340 } 341 ); 342 } 343 344 function setupAsyncZoom(options) { 345 var currentDoc = content.document; 346 var contentRootElement = currentDoc ? currentDoc.documentElement : null; 347 348 if ( 349 !contentRootElement || 350 !contentRootElement.hasAttribute("reftest-async-zoom") 351 ) { 352 return false; 353 } 354 355 var zoom = attrOrDefault(contentRootElement, "reftest-async-zoom", 1); 356 if (zoom != 1) { 357 try { 358 windowUtils().setAsyncZoom(contentRootElement, zoom); 359 return true; 360 } catch (e) { 361 if (!options.allowFailure) { 362 throw e; 363 } 364 } 365 } 366 return false; 367 } 368 369 function resetDisplayportAndViewport() { 370 // XXX currently the displayport configuration lives on the 371 // presshell and so is "reset" on nav when we get a new presshell. 372 } 373 374 function shouldWaitForPendingPaints() { 375 // if gHaveCanvasSnapshot is false, we're not taking snapshots so 376 // there is no need to wait for pending paints to be flushed. 377 return gHaveCanvasSnapshot && windowUtils().isMozAfterPaintPending; 378 } 379 380 function shouldWaitForReftestWaitRemoval(contentRootElement) { 381 // use getAttribute because className works differently in HTML and SVG 382 return ( 383 contentRootElement && 384 contentRootElement.hasAttribute("class") && 385 contentRootElement 386 .getAttribute("class") 387 .split(/\s+/) 388 .includes("reftest-wait") 389 ); 390 } 391 392 function shouldSnapshotWholePage(contentRootElement) { 393 // use getAttribute because className works differently in HTML and SVG 394 return ( 395 contentRootElement && 396 contentRootElement.hasAttribute("class") && 397 contentRootElement 398 .getAttribute("class") 399 .split(/\s+/) 400 .includes("reftest-snapshot-all") 401 ); 402 } 403 404 function shouldNotFlush(contentRootElement) { 405 // use getAttribute because className works differently in HTML and SVG 406 return ( 407 contentRootElement && 408 contentRootElement.hasAttribute("class") && 409 contentRootElement 410 .getAttribute("class") 411 .split(/\s+/) 412 .includes("reftest-no-flush") 413 ); 414 } 415 416 function getNoPaintElements(contentRootElement) { 417 return contentRootElement.getElementsByClassName("reftest-no-paint"); 418 } 419 function getNoDisplayListElements(contentRootElement) { 420 return contentRootElement.getElementsByClassName("reftest-no-display-list"); 421 } 422 function getDisplayListElements(contentRootElement) { 423 return contentRootElement.getElementsByClassName("reftest-display-list"); 424 } 425 426 function getOpaqueLayerElements(contentRootElement) { 427 return contentRootElement.getElementsByClassName("reftest-opaque-layer"); 428 } 429 430 function getAssignedLayerMap(contentRootElement) { 431 var layerNameToElementsMap = {}; 432 var elements = contentRootElement.querySelectorAll( 433 "[reftest-assigned-layer]" 434 ); 435 for (var i = 0; i < elements.length; ++i) { 436 var element = elements[i]; 437 var layerName = element.getAttribute("reftest-assigned-layer"); 438 if (!(layerName in layerNameToElementsMap)) { 439 layerNameToElementsMap[layerName] = []; 440 } 441 layerNameToElementsMap[layerName].push(element); 442 } 443 return layerNameToElementsMap; 444 } 445 446 const FlushMode = { 447 ALL: 0, 448 IGNORE_THROTTLED_ANIMATIONS: 1, 449 }; 450 451 // Initial state. When the document has loaded and all MozAfterPaint events and 452 // all explicit paint waits are flushed, we can fire the MozReftestInvalidate 453 // event and move to the next state. 454 const STATE_WAITING_TO_FIRE_INVALIDATE_EVENT = 0; 455 // When reftest-wait has been removed from the root element, we can move to the 456 // next state. 457 const STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL = 1; 458 // When spell checking is done on all spell-checked elements, we can move to the 459 // next state. 460 const STATE_WAITING_FOR_SPELL_CHECKS = 2; 461 // When any pending compositor-side repaint requests have been flushed, we can 462 // move to the next state. 463 const STATE_WAITING_FOR_APZ_FLUSH = 3; 464 // When all MozAfterPaint events and all explicit paint waits are flushed, we're 465 // done and can move to the COMPLETED state. 466 const STATE_WAITING_TO_FINISH = 4; 467 const STATE_COMPLETED = 5; 468 469 async function FlushRendering(aFlushMode) { 470 let browsingContext = content.docShell.browsingContext; 471 let ignoreThrottledAnimations = 472 aFlushMode === FlushMode.IGNORE_THROTTLED_ANIMATIONS; 473 // Ensure the refresh driver ticks at least once, this ensures some 474 // preference changes take effect. 475 let needsAnimationFrame = IsSnapshottableTestType(); 476 try { 477 let result = await content.windowGlobalChild 478 .getActor("ReftestFission") 479 .sendQuery("FlushRendering", { 480 browsingContext, 481 ignoreThrottledAnimations, 482 needsAnimationFrame, 483 }); 484 for (let errorString of result.errorStrings) { 485 LogError(errorString); 486 } 487 for (let warningString of result.warningStrings) { 488 LogWarning(warningString); 489 } 490 for (let infoString of result.infoStrings) { 491 LogInfo(infoString); 492 } 493 } catch (reason) { 494 // We expect actors to go away causing sendQuery's to fail, so 495 // just note it. 496 LogInfo("FlushRendering sendQuery to parent rejected: " + reason); 497 } 498 } 499 500 function WaitForTestEnd( 501 contentRootElement, 502 inPrintMode, 503 spellCheckedElements, 504 forURL 505 ) { 506 // WaitForTestEnd works via the MakeProgress function below. It is responsible for 507 // moving through the states listed above and calling FlushRendering. We also listen 508 // for a number of events, the most important of which is the AfterPaintListener, 509 // which is responsible for updating the canvas after paints. In a fission world 510 // FlushRendering and updating the canvas must necessarily be async operations. 511 // During these async operations we want to wait for them to finish and we don't 512 // want to try to do anything else (what would we even want to do while only some of 513 // the processes involved have flushed layout or updated their layer trees?). So 514 // we call OperationInProgress whenever we are about to go back to the event loop 515 // during one of these calls, and OperationCompleted when it finishes. This prevents 516 // anything else from running while we wait and getting us into a confused state. We 517 // then record anything that happens while we are waiting to make sure that the 518 // right actions are triggered. The possible actions are basically calling 519 // MakeProgress from a setTimeout, and updating the canvas for an after paint event. 520 // The after paint listener just stashes the rects and we update them after a 521 // completed MakeProgress call. This is handled by 522 // HandlePendingTasksAfterMakeProgress, which also waits for any pending after paint 523 // events. The general sequence of events is: 524 // - MakeProgress 525 // - HandlePendingTasksAfterMakeProgress 526 // - wait for after paint event if one is pending 527 // - update canvas for after paint events we have received 528 // - MakeProgress 529 // etc 530 531 function CheckForLivenessOfContentRootElement() { 532 if (contentRootElement && Cu.isDeadWrapper(contentRootElement)) { 533 contentRootElement = null; 534 } 535 } 536 537 var setTimeoutCallMakeProgressWhenComplete = false; 538 539 var operationInProgress = false; 540 function OperationInProgress() { 541 if (operationInProgress) { 542 LogWarning("Nesting atomic operations?"); 543 } 544 operationInProgress = true; 545 } 546 function OperationCompleted() { 547 if (!operationInProgress) { 548 LogWarning("Mismatched OperationInProgress/OperationCompleted calls?"); 549 } 550 operationInProgress = false; 551 if (setTimeoutCallMakeProgressWhenComplete) { 552 setTimeoutCallMakeProgressWhenComplete = false; 553 setTimeout(CallMakeProgress, 0); 554 } 555 } 556 function AssertNoOperationInProgress() { 557 if (operationInProgress) { 558 LogWarning("AssertNoOperationInProgress but operationInProgress"); 559 } 560 } 561 562 var updateCanvasPending = false; 563 var updateCanvasRects = []; 564 565 var currentDoc = content.document; 566 var state = STATE_WAITING_TO_FIRE_INVALIDATE_EVENT; 567 568 var setTimeoutMakeProgressPending = false; 569 570 function CallSetTimeoutMakeProgress() { 571 if (setTimeoutMakeProgressPending) { 572 return; 573 } 574 setTimeoutMakeProgressPending = true; 575 setTimeout(CallMakeProgress, 0); 576 } 577 578 // This should only ever be called from a timeout. 579 function CallMakeProgress() { 580 if (operationInProgress) { 581 setTimeoutCallMakeProgressWhenComplete = true; 582 return; 583 } 584 setTimeoutMakeProgressPending = false; 585 MakeProgress(); 586 } 587 588 var waitingForAnAfterPaint = false; 589 590 // Updates the canvas if there are pending updates for it. Checks if we 591 // need to call MakeProgress. 592 function HandlePendingTasksAfterMakeProgress() { 593 AssertNoOperationInProgress(); 594 595 if ( 596 (state == STATE_WAITING_TO_FIRE_INVALIDATE_EVENT || 597 state == STATE_WAITING_TO_FINISH) && 598 shouldWaitForPendingPaints() 599 ) { 600 LogInfo( 601 "HandlePendingTasksAfterMakeProgress waiting for a MozAfterPaint" 602 ); 603 // We are in a state where we wait for MozAfterPaint to clear and a 604 // MozAfterPaint event is pending, give it a chance to fire, but don't 605 // let anything else run. 606 waitingForAnAfterPaint = true; 607 OperationInProgress(); 608 return; 609 } 610 611 if (updateCanvasPending) { 612 LogInfo("HandlePendingTasksAfterMakeProgress updating canvas"); 613 updateCanvasPending = false; 614 let rects = updateCanvasRects; 615 updateCanvasRects = []; 616 OperationInProgress(); 617 CheckForLivenessOfContentRootElement(); 618 let promise = SendUpdateCanvasForEvent(forURL, rects, contentRootElement); 619 promise.then(function () { 620 OperationCompleted(); 621 // After paint events are fired immediately after a paint (one 622 // of the things that can call us). Don't confuse ourselves by 623 // firing synchronously if we triggered the paint ourselves. 624 CallSetTimeoutMakeProgress(); 625 }); 626 } 627 } 628 629 // true if rectA contains rectB 630 function Contains(rectA, rectB) { 631 return ( 632 rectA.left <= rectB.left && 633 rectB.right <= rectA.right && 634 rectA.top <= rectB.top && 635 rectB.bottom <= rectA.bottom 636 ); 637 } 638 // true if some rect in rectList contains rect 639 function ContainedIn(rectList, rect) { 640 for (let i = 0; i < rectList.length; ++i) { 641 if (Contains(rectList[i], rect)) { 642 return true; 643 } 644 } 645 return false; 646 } 647 648 function AfterPaintListener(event) { 649 LogInfo("AfterPaintListener in " + event.target.document.location.href); 650 if (event.target.document != currentDoc) { 651 // ignore paint events for subframes or old documents in the window. 652 // Invalidation in subframes will cause invalidation in the toplevel document anyway. 653 return; 654 } 655 656 updateCanvasPending = true; 657 for (let r of event.clientRects) { 658 if (ContainedIn(updateCanvasRects, r)) { 659 continue; 660 } 661 662 // Copy the rect; it's content and we are chrome, which means if the 663 // document goes away (and it can in some crashtests) our reference 664 // to it will be turned into a dead wrapper that we can't acccess. 665 updateCanvasRects.push({ 666 left: r.left, 667 top: r.top, 668 right: r.right, 669 bottom: r.bottom, 670 }); 671 } 672 673 if (waitingForAnAfterPaint) { 674 waitingForAnAfterPaint = false; 675 OperationCompleted(); 676 } 677 678 if (!operationInProgress) { 679 HandlePendingTasksAfterMakeProgress(); 680 } 681 // Otherwise we know that eventually after the operation finishes we 682 // will get a MakeProgress and/or HandlePendingTasksAfterMakeProgress 683 // call, so we don't need to do anything. 684 } 685 686 function FromChildAfterPaintListener(event) { 687 LogInfo( 688 "FromChildAfterPaintListener from " + event.detail.originalTargetUri 689 ); 690 691 updateCanvasPending = true; 692 for (let r of event.detail.rects) { 693 if (ContainedIn(updateCanvasRects, r)) { 694 continue; 695 } 696 697 // Copy the rect; it's content and we are chrome, which means if the 698 // document goes away (and it can in some crashtests) our reference 699 // to it will be turned into a dead wrapper that we can't acccess. 700 updateCanvasRects.push({ 701 left: r.left, 702 top: r.top, 703 right: r.right, 704 bottom: r.bottom, 705 }); 706 } 707 708 if (!operationInProgress) { 709 HandlePendingTasksAfterMakeProgress(); 710 } 711 // Otherwise we know that eventually after the operation finishes we 712 // will get a MakeProgress and/or HandlePendingTasksAfterMakeProgress 713 // call, so we don't need to do anything. 714 } 715 716 let attrModifiedObserver; 717 function AttrModifiedListener() { 718 LogInfo("AttrModifiedListener fired"); 719 // Wait for the next return-to-event-loop before continuing --- for 720 // example, the attribute may have been modified in an subdocument's 721 // load event handler, in which case we need load event processing 722 // to complete and unsuppress painting before we check isMozAfterPaintPending. 723 CallSetTimeoutMakeProgress(); 724 } 725 726 function RemoveListeners() { 727 // OK, we can end the test now. 728 removeEventListener("MozAfterPaint", AfterPaintListener, false); 729 removeEventListener( 730 "Reftest:MozAfterPaintFromChild", 731 FromChildAfterPaintListener, 732 false 733 ); 734 CheckForLivenessOfContentRootElement(); 735 if (attrModifiedObserver) { 736 if (!Cu.isDeadWrapper(attrModifiedObserver)) { 737 attrModifiedObserver.disconnect(); 738 } 739 attrModifiedObserver = null; 740 } 741 gTimeoutHook = null; 742 // Make sure we're in the COMPLETED state just in case 743 // (this may be called via the test-timeout hook) 744 state = STATE_COMPLETED; 745 } 746 747 // Everything that could cause shouldWaitForXXX() to 748 // change from returning true to returning false is monitored via some kind 749 // of event listener which eventually calls this function. 750 function MakeProgress() { 751 if (state >= STATE_COMPLETED) { 752 LogInfo("MakeProgress: STATE_COMPLETED"); 753 return; 754 } 755 756 LogInfo("MakeProgress"); 757 758 // We don't need to flush styles any more when we are in the state 759 // after reftest-wait has removed. 760 OperationInProgress(); 761 let promise = Promise.resolve(undefined); 762 if (state != STATE_WAITING_TO_FINISH) { 763 // If we are waiting for the MozReftestInvalidate event we don't want 764 // to flush throttled animations. Flushing throttled animations can 765 // continue to cause new MozAfterPaint events even when all the 766 // rendering we're concerned about should have ceased. Since 767 // MozReftestInvalidate won't be sent until we finish waiting for all 768 // MozAfterPaint events, we should avoid flushing throttled animations 769 // here or else we'll never leave this state. 770 let flushMode = 771 state === STATE_WAITING_TO_FIRE_INVALIDATE_EVENT 772 ? FlushMode.IGNORE_THROTTLED_ANIMATIONS 773 : FlushMode.ALL; 774 promise = FlushRendering(flushMode); 775 } 776 promise.then(function () { 777 OperationCompleted(); 778 MakeProgress2(); 779 // If there is an operation in progress then we know there will be 780 // a MakeProgress call is will happen after it finishes. 781 if (!operationInProgress) { 782 HandlePendingTasksAfterMakeProgress(); 783 } 784 }); 785 } 786 787 // eslint-disable-next-line complexity 788 function MakeProgress2() { 789 switch (state) { 790 case STATE_WAITING_TO_FIRE_INVALIDATE_EVENT: { 791 LogInfo("MakeProgress: STATE_WAITING_TO_FIRE_INVALIDATE_EVENT"); 792 if (shouldWaitForPendingPaints() || updateCanvasPending) { 793 gFailureReason = 794 "timed out waiting for pending paint count to reach zero"; 795 if (shouldWaitForPendingPaints()) { 796 gFailureReason += " (waiting for MozAfterPaint)"; 797 LogInfo("MakeProgress: waiting for MozAfterPaint"); 798 } 799 if (updateCanvasPending) { 800 gFailureReason += " (waiting for updateCanvasPending)"; 801 LogInfo("MakeProgress: waiting for updateCanvasPending"); 802 } 803 return; 804 } 805 806 state = STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL; 807 CheckForLivenessOfContentRootElement(); 808 var hasReftestWait = 809 shouldWaitForReftestWaitRemoval(contentRootElement); 810 // Notify the test document that now is a good time to test some invalidation 811 LogInfo("MakeProgress: dispatching MozReftestInvalidate"); 812 if (contentRootElement) { 813 let elements = getNoPaintElements(contentRootElement); 814 for (let i = 0; i < elements.length; ++i) { 815 windowUtils().checkAndClearPaintedState(elements[i]); 816 } 817 elements = getNoDisplayListElements(contentRootElement); 818 for (let i = 0; i < elements.length; ++i) { 819 windowUtils().checkAndClearDisplayListState(elements[i]); 820 } 821 elements = getDisplayListElements(contentRootElement); 822 for (let i = 0; i < elements.length; ++i) { 823 windowUtils().checkAndClearDisplayListState(elements[i]); 824 } 825 var notification = content.document.createEvent("Events"); 826 notification.initEvent("MozReftestInvalidate", true, false); 827 contentRootElement.dispatchEvent(notification); 828 } else { 829 LogInfo( 830 "MakeProgress: couldn't send MozReftestInvalidate event because content root element does not exist" 831 ); 832 } 833 834 CheckForLivenessOfContentRootElement(); 835 if (!inPrintMode && doPrintMode(contentRootElement)) { 836 LogInfo("MakeProgress: setting up print mode"); 837 setupPrintMode(contentRootElement); 838 } 839 840 CheckForLivenessOfContentRootElement(); 841 if ( 842 hasReftestWait && 843 !shouldWaitForReftestWaitRemoval(contentRootElement) 844 ) { 845 // MozReftestInvalidate handler removed reftest-wait. 846 // We expect something to have been invalidated... 847 OperationInProgress(); 848 let promise = FlushRendering(FlushMode.ALL); 849 promise.then(function () { 850 OperationCompleted(); 851 if (!updateCanvasPending && !shouldWaitForPendingPaints()) { 852 LogWarning("MozInvalidateEvent didn't invalidate"); 853 } 854 MakeProgress(); 855 }); 856 return; 857 } 858 // Try next state 859 MakeProgress(); 860 return; 861 } 862 863 case STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL: 864 LogInfo("MakeProgress: STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL"); 865 CheckForLivenessOfContentRootElement(); 866 if (shouldWaitForReftestWaitRemoval(contentRootElement)) { 867 gFailureReason = "timed out waiting for reftest-wait to be removed"; 868 LogInfo("MakeProgress: waiting for reftest-wait to be removed"); 869 return; 870 } 871 872 if (shouldNotFlush(contentRootElement)) { 873 // If reftest-no-flush is specified, we need to set 874 // updateCanvasPending explicitly to take the latest snapshot 875 // since animation changes on the compositor thread don't invoke 876 // any MozAfterPaint events at all. 877 // NOTE: We don't add any rects to updateCanvasRects here since 878 // SendUpdateCanvasForEvent() will handle this case properly 879 // without any rects. 880 updateCanvasPending = true; 881 } 882 // Try next state 883 state = STATE_WAITING_FOR_SPELL_CHECKS; 884 MakeProgress(); 885 return; 886 887 case STATE_WAITING_FOR_SPELL_CHECKS: 888 LogInfo("MakeProgress: STATE_WAITING_FOR_SPELL_CHECKS"); 889 if (numPendingSpellChecks) { 890 gFailureReason = "timed out waiting for spell checks to end"; 891 LogInfo("MakeProgress: waiting for spell checks to end"); 892 return; 893 } 894 895 state = STATE_WAITING_FOR_APZ_FLUSH; 896 LogInfo("MakeProgress: STATE_WAITING_FOR_APZ_FLUSH"); 897 gFailureReason = "timed out waiting for APZ flush to complete"; 898 899 var flushWaiter = function (aSubject, aTopic) { 900 if (aTopic) { 901 LogInfo("MakeProgress: apz-repaints-flushed fired"); 902 } 903 Services.obs.removeObserver(flushWaiter, "apz-repaints-flushed"); 904 state = STATE_WAITING_TO_FINISH; 905 if (operationInProgress) { 906 CallSetTimeoutMakeProgress(); 907 } else { 908 MakeProgress(); 909 } 910 }; 911 Services.obs.addObserver(flushWaiter, "apz-repaints-flushed"); 912 913 var willSnapshot = IsSnapshottableTestType(); 914 CheckForLivenessOfContentRootElement(); 915 var noFlush = !shouldNotFlush(contentRootElement); 916 if (noFlush && willSnapshot && windowUtils().flushApzRepaints()) { 917 LogInfo("MakeProgress: done requesting APZ flush"); 918 } else { 919 LogInfo("MakeProgress: APZ flush not required"); 920 flushWaiter(null, null, null); 921 } 922 return; 923 924 case STATE_WAITING_FOR_APZ_FLUSH: 925 LogInfo("MakeProgress: STATE_WAITING_FOR_APZ_FLUSH"); 926 // Nothing to do here; once we get the apz-repaints-flushed event 927 // we will go to STATE_WAITING_TO_FINISH 928 return; 929 930 case STATE_WAITING_TO_FINISH: 931 LogInfo("MakeProgress: STATE_WAITING_TO_FINISH"); 932 if (shouldWaitForPendingPaints() || updateCanvasPending) { 933 gFailureReason = 934 "timed out waiting for pending paint count to " + 935 "reach zero (after reftest-wait removed and switch to print mode)"; 936 if (shouldWaitForPendingPaints()) { 937 gFailureReason += " (waiting for MozAfterPaint)"; 938 LogInfo("MakeProgress: waiting for MozAfterPaint"); 939 } 940 if (updateCanvasPending) { 941 gFailureReason += " (waiting for updateCanvasPending)"; 942 LogInfo("MakeProgress: waiting for updateCanvasPending"); 943 } 944 return; 945 } 946 CheckForLivenessOfContentRootElement(); 947 if (contentRootElement) { 948 let elements = getNoPaintElements(contentRootElement); 949 for (let i = 0; i < elements.length; ++i) { 950 if (windowUtils().checkAndClearPaintedState(elements[i])) { 951 SendFailedNoPaint(); 952 } 953 } 954 // We only support retained display lists in the content process 955 // right now, so don't fail reftest-no-display-list tests when 956 // we don't have e10s. 957 if (gBrowserIsRemote) { 958 elements = getNoDisplayListElements(contentRootElement); 959 for (let i = 0; i < elements.length; ++i) { 960 if (windowUtils().checkAndClearDisplayListState(elements[i])) { 961 SendFailedNoDisplayList(); 962 } 963 } 964 elements = getDisplayListElements(contentRootElement); 965 for (let i = 0; i < elements.length; ++i) { 966 if (!windowUtils().checkAndClearDisplayListState(elements[i])) { 967 SendFailedDisplayList(); 968 } 969 } 970 } 971 } 972 973 if (!IsSnapshottableTestType()) { 974 // If we're not snapshotting the test, at least do a sync round-trip 975 // to the compositor to ensure that all the rendering messages 976 // related to this test get processed. Otherwise problems triggered 977 // by this test may only manifest as failures in a later test. 978 LogInfo("MakeProgress: Doing sync flush to compositor"); 979 gFailureReason = "timed out while waiting for sync compositor flush"; 980 windowUtils().syncFlushCompositor(); 981 } 982 983 LogInfo("MakeProgress: Completed"); 984 state = STATE_COMPLETED; 985 gFailureReason = "timed out while taking snapshot (bug in harness?)"; 986 RemoveListeners(); 987 CheckForLivenessOfContentRootElement(); 988 CheckForProcessCrashExpectation(contentRootElement); 989 setTimeout(RecordResult, 0, forURL); 990 } 991 } 992 993 LogInfo("WaitForTestEnd: Adding listeners"); 994 addEventListener("MozAfterPaint", AfterPaintListener, false); 995 addEventListener( 996 "Reftest:MozAfterPaintFromChild", 997 FromChildAfterPaintListener, 998 false 999 ); 1000 1001 // If contentRootElement is null then shouldWaitForReftestWaitRemoval will 1002 // always return false so we don't need a listener anyway 1003 CheckForLivenessOfContentRootElement(); 1004 if (contentRootElement?.hasAttribute("class")) { 1005 attrModifiedObserver = 1006 // ownerGlobal doesn't exist in content windows. 1007 // eslint-disable-next-line mozilla/use-ownerGlobal 1008 new contentRootElement.ownerDocument.defaultView.MutationObserver( 1009 AttrModifiedListener 1010 ); 1011 attrModifiedObserver.observe(contentRootElement, { attributes: true }); 1012 } 1013 gTimeoutHook = RemoveListeners; 1014 1015 // Listen for spell checks on spell-checked elements. 1016 var numPendingSpellChecks = spellCheckedElements.length; 1017 function decNumPendingSpellChecks() { 1018 --numPendingSpellChecks; 1019 if (operationInProgress) { 1020 CallSetTimeoutMakeProgress(); 1021 } else { 1022 MakeProgress(); 1023 } 1024 } 1025 for (let editable of spellCheckedElements) { 1026 try { 1027 onSpellCheck(editable, decNumPendingSpellChecks); 1028 } catch (err) { 1029 // The element may not have an editor, so ignore it. 1030 setTimeout(decNumPendingSpellChecks, 0); 1031 } 1032 } 1033 1034 // Take a full snapshot now that all our listeners are set up. This 1035 // ensures it's impossible for us to miss updates between taking the snapshot 1036 // and adding our listeners. 1037 OperationInProgress(); 1038 let promise = SendInitCanvasWithSnapshot(forURL); 1039 promise.then(function () { 1040 OperationCompleted(); 1041 MakeProgress(); 1042 }); 1043 } 1044 1045 async function OnDocumentLoad(uri) { 1046 if (gClearingForAssertionCheck) { 1047 if (uri == BLANK_URL_FOR_CLEARING) { 1048 DoAssertionCheck(); 1049 return; 1050 } 1051 1052 // It's likely the previous test document reloads itself and causes the 1053 // attempt of loading blank page fails. In this case we should retry 1054 // loading the blank page. 1055 LogInfo("Retry loading a blank page"); 1056 setTimeout(LoadURI, 0, BLANK_URL_FOR_CLEARING); 1057 return; 1058 } 1059 1060 if (uri != gCurrentURL) { 1061 LogInfo("OnDocumentLoad fired for previous document"); 1062 // Ignore load events for previous documents. 1063 return; 1064 } 1065 1066 var currentDoc = content && content.document; 1067 1068 // Collect all editable, spell-checked elements. It may be the case that 1069 // not all the elements that match this selector will be spell checked: for 1070 // example, a textarea without a spellcheck attribute may have a parent with 1071 // spellcheck=false, or script may set spellcheck=false on an element whose 1072 // markup sets it to true. But that's OK since onSpellCheck detects the 1073 // absence of spell checking, too. 1074 var querySelector = 1075 '*[class~="spell-checked"],' + 1076 'textarea:not([spellcheck="false"]),' + 1077 'input[spellcheck]:-moz-any([spellcheck=""],[spellcheck="true"]),' + 1078 '*[contenteditable]:-moz-any([contenteditable=""],[contenteditable="true"])'; 1079 var spellCheckedElements = currentDoc 1080 ? currentDoc.querySelectorAll(querySelector) 1081 : []; 1082 1083 var contentRootElement = currentDoc ? currentDoc.documentElement : null; 1084 currentDoc = null; 1085 setupFullZoom(contentRootElement); 1086 setupTextZoom(contentRootElement); 1087 setupViewport(contentRootElement); 1088 await setupDisplayport(contentRootElement); 1089 var inPrintMode = false; 1090 1091 async function AfterOnLoadScripts() { 1092 // Regrab the root element, because the document may have changed. 1093 var contentRootElement = content.document 1094 ? content.document.documentElement 1095 : null; 1096 1097 // Flush the document in case it got modified in a load event handler. 1098 await FlushRendering(FlushMode.ALL); 1099 1100 // Take a snapshot now. 1101 let painted = await SendInitCanvasWithSnapshot(uri); 1102 1103 if (contentRootElement && Cu.isDeadWrapper(contentRootElement)) { 1104 contentRootElement = null; 1105 } 1106 1107 if ( 1108 (!inPrintMode && doPrintMode(contentRootElement)) || 1109 // If we didn't force a paint above, in 1110 // InitCurrentCanvasWithSnapshot, so we should wait for a 1111 // paint before we consider them done. 1112 !painted 1113 ) { 1114 LogInfo("AfterOnLoadScripts belatedly entering WaitForTestEnd"); 1115 // Go into reftest-wait mode belatedly. 1116 WaitForTestEnd(contentRootElement, inPrintMode, [], uri); 1117 } else { 1118 CheckForProcessCrashExpectation(contentRootElement); 1119 RecordResult(uri); 1120 } 1121 } 1122 1123 if ( 1124 shouldWaitForReftestWaitRemoval(contentRootElement) || 1125 spellCheckedElements.length 1126 ) { 1127 // Go into reftest-wait mode immediately after painting has been 1128 // unsuppressed, after the onload event has finished dispatching. 1129 gFailureReason = 1130 "timed out waiting for test to complete (trying to get into WaitForTestEnd)"; 1131 LogInfo("OnDocumentLoad triggering WaitForTestEnd"); 1132 setTimeout(function () { 1133 WaitForTestEnd( 1134 contentRootElement, 1135 inPrintMode, 1136 spellCheckedElements, 1137 uri 1138 ); 1139 }, 0); 1140 } else { 1141 if (doPrintMode(contentRootElement)) { 1142 LogInfo("OnDocumentLoad setting up print mode"); 1143 setupPrintMode(contentRootElement); 1144 inPrintMode = true; 1145 } 1146 1147 // Since we can't use a bubbling-phase load listener from chrome, 1148 // this is a capturing phase listener. So do setTimeout twice, the 1149 // first to get us after the onload has fired in the content, and 1150 // the second to get us after any setTimeout(foo, 0) in the content. 1151 gFailureReason = 1152 "timed out waiting for test to complete (waiting for onload scripts to complete)"; 1153 LogInfo("OnDocumentLoad triggering AfterOnLoadScripts"); 1154 setTimeout(function () { 1155 setTimeout(AfterOnLoadScripts, 0); 1156 }, 0); 1157 } 1158 } 1159 1160 function CheckForProcessCrashExpectation(contentRootElement) { 1161 if ( 1162 contentRootElement && 1163 contentRootElement.hasAttribute("class") && 1164 contentRootElement 1165 .getAttribute("class") 1166 .split(/\s+/) 1167 .includes("reftest-expect-process-crash") 1168 ) { 1169 SendExpectProcessCrash(); 1170 } 1171 } 1172 1173 async function RecordResult(forURL) { 1174 if (forURL != gCurrentURL) { 1175 LogInfo("RecordResult fired for previous document"); 1176 return; 1177 } 1178 1179 if (gCurrentURLRecordResults > 0) { 1180 LogInfo("RecordResult fired extra times"); 1181 FinishTestItem(); 1182 return; 1183 } 1184 gCurrentURLRecordResults++; 1185 1186 LogInfo("RecordResult fired"); 1187 1188 var currentTestRunTime = Date.now() - gCurrentTestStartTime; 1189 1190 clearTimeout(gFailureTimeout); 1191 gFailureReason = null; 1192 gFailureTimeout = null; 1193 gCurrentURL = null; 1194 gCurrentURLTargetType = undefined; 1195 1196 if (gCurrentTestType == TYPE_PRINT) { 1197 printToPdf(); 1198 return; 1199 } 1200 if (gCurrentTestType == TYPE_SCRIPT) { 1201 var error = ""; 1202 var testwindow = content; 1203 1204 if (testwindow.wrappedJSObject) { 1205 testwindow = testwindow.wrappedJSObject; 1206 } 1207 1208 var testcases; 1209 if ( 1210 !testwindow.getTestCases || 1211 typeof testwindow.getTestCases != "function" 1212 ) { 1213 // Force an unexpected failure to alert the test author to fix the test. 1214 error = "test must provide a function getTestCases(). (SCRIPT)\n"; 1215 } else if (!(testcases = testwindow.getTestCases())) { 1216 // Force an unexpected failure to alert the test author to fix the test. 1217 error = 1218 "test's getTestCases() must return an Array-like Object. (SCRIPT)\n"; 1219 } else if (!testcases.length) { 1220 // This failure may be due to a JavaScript Engine bug causing 1221 // early termination of the test. If we do not allow silent 1222 // failure, the driver will report an error. 1223 } 1224 1225 var results = []; 1226 if (!error) { 1227 // FIXME/bug 618176: temporary workaround 1228 for (var i = 0; i < testcases.length; ++i) { 1229 var test = testcases[i]; 1230 results.push({ 1231 passed: test.testPassed(), 1232 description: test.testDescription(), 1233 }); 1234 } 1235 //results = testcases.map(function(test) { 1236 // return { passed: test.testPassed(), 1237 // description: test.testDescription() }; 1238 } 1239 1240 SendScriptResults(currentTestRunTime, error, results); 1241 FinishTestItem(); 1242 return; 1243 } 1244 1245 // Setup async scroll offsets now in case SynchronizeForSnapshot is not 1246 // called (due to reftest-no-sync-layers being supplied, or in the single 1247 // process case). 1248 let changedAsyncScrollZoom = await setupAsyncScrollOffsets({ 1249 allowFailure: true, 1250 }); 1251 if (setupAsyncZoom({ allowFailure: true })) { 1252 changedAsyncScrollZoom = true; 1253 } 1254 if (changedAsyncScrollZoom && !gBrowserIsRemote) { 1255 sendAsyncMessage("reftest:UpdateWholeCanvasForInvalidation"); 1256 } 1257 1258 SendTestDone(currentTestRunTime); 1259 FinishTestItem(); 1260 } 1261 1262 function LoadFailed() { 1263 if (gTimeoutHook) { 1264 gTimeoutHook(); 1265 } 1266 gFailureTimeout = null; 1267 SendFailedLoad(gFailureReason); 1268 } 1269 1270 function FinishTestItem() { 1271 gHaveCanvasSnapshot = false; 1272 } 1273 1274 function DoAssertionCheck() { 1275 gClearingForAssertionCheck = false; 1276 1277 var numAsserts = 0; 1278 if (gDebug.isDebugBuild) { 1279 var newAssertionCount = gDebug.assertionCount; 1280 numAsserts = newAssertionCount - gAssertionCount; 1281 gAssertionCount = newAssertionCount; 1282 } 1283 SendAssertionCount(numAsserts); 1284 } 1285 1286 function LoadURI(uri) { 1287 let loadURIOptions = { 1288 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), 1289 }; 1290 webNavigation().loadURI(Services.io.newURI(uri), loadURIOptions); 1291 } 1292 1293 function LogError(str) { 1294 if (gVerbose) { 1295 sendSyncMessage("reftest:Log", { type: "error", msg: str }); 1296 } else { 1297 sendAsyncMessage("reftest:Log", { type: "error", msg: str }); 1298 } 1299 } 1300 1301 function LogWarning(str) { 1302 if (gVerbose) { 1303 sendSyncMessage("reftest:Log", { type: "warning", msg: str }); 1304 } else { 1305 sendAsyncMessage("reftest:Log", { type: "warning", msg: str }); 1306 } 1307 } 1308 1309 function LogInfo(str) { 1310 if (gVerbose) { 1311 sendSyncMessage("reftest:Log", { type: "info", msg: str }); 1312 } else { 1313 sendAsyncMessage("reftest:Log", { type: "info", msg: str }); 1314 } 1315 } 1316 1317 function IsSnapshottableTestType() { 1318 // Script, load-only, and PDF-print tests do not need any snapshotting. 1319 return !( 1320 gCurrentTestType == TYPE_SCRIPT || 1321 gCurrentTestType == TYPE_LOAD || 1322 gCurrentTestType == TYPE_PRINT 1323 ); 1324 } 1325 1326 const SYNC_DEFAULT = 0x0; 1327 const SYNC_ALLOW_DISABLE = 0x1; 1328 // Returns a promise that resolve when the snapshot is done. 1329 function SynchronizeForSnapshot(flags) { 1330 if (!IsSnapshottableTestType()) { 1331 return Promise.resolve(undefined); 1332 } 1333 1334 if (flags & SYNC_ALLOW_DISABLE) { 1335 var docElt = content.document.documentElement; 1336 if ( 1337 docElt && 1338 (docElt.hasAttribute("reftest-no-sync-layers") || shouldNotFlush(docElt)) 1339 ) { 1340 LogInfo("Test file chose to skip SynchronizeForSnapshot"); 1341 return Promise.resolve(undefined); 1342 } 1343 } 1344 1345 let browsingContext = content.docShell.browsingContext; 1346 let promise = content.windowGlobalChild 1347 .getActor("ReftestFission") 1348 .sendQuery("UpdateLayerTree", { browsingContext }); 1349 return promise.then( 1350 function (result) { 1351 for (let errorString of result.errorStrings) { 1352 LogError(errorString); 1353 } 1354 for (let infoString of result.infoStrings) { 1355 LogInfo(infoString); 1356 } 1357 1358 // Setup async scroll offsets now, because any scrollable layers should 1359 // have had their AsyncPanZoomControllers created. 1360 return setupAsyncScrollOffsets({ allowFailure: false }).then(function () { 1361 setupAsyncZoom({ allowFailure: false }); 1362 }); 1363 }, 1364 function (reason) { 1365 // We expect actors to go away causing sendQuery's to fail, so 1366 // just note it. 1367 LogInfo("UpdateLayerTree sendQuery to parent rejected: " + reason); 1368 1369 // Setup async scroll offsets now, because any scrollable layers should 1370 // have had their AsyncPanZoomControllers created. 1371 return setupAsyncScrollOffsets({ allowFailure: false }).then(function () { 1372 setupAsyncZoom({ allowFailure: false }); 1373 }); 1374 } 1375 ); 1376 } 1377 1378 function RegisterMessageListeners() { 1379 addMessageListener("reftest:Clear", function () { 1380 RecvClear(); 1381 }); 1382 addMessageListener("reftest:LoadScriptTest", function (m) { 1383 RecvLoadScriptTest(m.json.uri, m.json.timeout); 1384 }); 1385 addMessageListener("reftest:LoadPrintTest", function (m) { 1386 RecvLoadPrintTest(m.json.uri, m.json.timeout); 1387 }); 1388 addMessageListener("reftest:LoadTest", function (m) { 1389 RecvLoadTest(m.json.type, m.json.uri, m.json.uriTargetType, m.json.timeout); 1390 }); 1391 addMessageListener("reftest:ResetRenderingState", function () { 1392 RecvResetRenderingState(); 1393 }); 1394 addMessageListener("reftest:PrintDone", function (m) { 1395 RecvPrintDone(m.json.status, m.json.fileName); 1396 }); 1397 addMessageListener("reftest:UpdateCanvasWithSnapshotDone", function (m) { 1398 RecvUpdateCanvasWithSnapshotDone(m.json.painted); 1399 }); 1400 } 1401 1402 function RecvClear() { 1403 gClearingForAssertionCheck = true; 1404 LoadURI(BLANK_URL_FOR_CLEARING); 1405 } 1406 1407 function RecvLoadTest(type, uri, uriTargetType, timeout) { 1408 StartTestURI(type, uri, uriTargetType, timeout); 1409 } 1410 1411 function RecvLoadScriptTest(uri, timeout) { 1412 StartTestURI(TYPE_SCRIPT, uri, URL_TARGET_TYPE_TEST, timeout); 1413 } 1414 1415 function RecvLoadPrintTest(uri, timeout) { 1416 StartTestURI(TYPE_PRINT, uri, URL_TARGET_TYPE_TEST, timeout); 1417 } 1418 1419 function RecvResetRenderingState() { 1420 resetZoomAndTextZoom(); 1421 resetDisplayportAndViewport(); 1422 } 1423 1424 function RecvPrintDone(status, fileName) { 1425 const currentTestRunTime = Date.now() - gCurrentTestStartTime; 1426 SendPrintResult(currentTestRunTime, status, fileName); 1427 FinishTestItem(); 1428 } 1429 1430 function RecvUpdateCanvasWithSnapshotDone(painted) { 1431 gUpdateCanvasPromiseResolver(painted); 1432 } 1433 1434 function SendAssertionCount(numAssertions) { 1435 sendAsyncMessage("reftest:AssertionCount", { count: numAssertions }); 1436 } 1437 1438 function SendContentReady() { 1439 let gfxInfo = 1440 NS_GFXINFO_CONTRACTID in Cc && 1441 Cc[NS_GFXINFO_CONTRACTID].getService(Ci.nsIGfxInfo); 1442 1443 let info = {}; 1444 1445 try { 1446 info.DWriteEnabled = gfxInfo.DWriteEnabled; 1447 info.EmbeddedInFirefoxReality = gfxInfo.EmbeddedInFirefoxReality; 1448 } catch (e) { 1449 info.DWriteEnabled = false; 1450 info.EmbeddedInFirefoxReality = false; 1451 } 1452 1453 info.AzureCanvasBackend = gfxInfo.AzureCanvasBackend; 1454 info.AzureContentBackend = gfxInfo.AzureContentBackend; 1455 1456 return sendSyncMessage("reftest:ContentReady", { gfx: info })[0]; 1457 } 1458 1459 function SendException(what) { 1460 sendAsyncMessage("reftest:Exception", { what }); 1461 } 1462 1463 function SendFailedLoad(why) { 1464 sendAsyncMessage("reftest:FailedLoad", { why }); 1465 } 1466 1467 function SendFailedNoPaint() { 1468 sendAsyncMessage("reftest:FailedNoPaint"); 1469 } 1470 1471 function SendFailedNoDisplayList() { 1472 sendAsyncMessage("reftest:FailedNoDisplayList"); 1473 } 1474 1475 function SendFailedDisplayList() { 1476 sendAsyncMessage("reftest:FailedDisplayList"); 1477 } 1478 1479 function SendFailedOpaqueLayer(why) { 1480 sendAsyncMessage("reftest:FailedOpaqueLayer", { why }); 1481 } 1482 1483 function SendFailedAssignedLayer(why) { 1484 sendAsyncMessage("reftest:FailedAssignedLayer", { why }); 1485 } 1486 1487 // Returns a promise that resolves to a bool that indicates if a snapshot was taken. 1488 async function SendInitCanvasWithSnapshot(forURL) { 1489 if (forURL != gCurrentURL) { 1490 LogInfo("SendInitCanvasWithSnapshot called for previous document"); 1491 // Lie and say we painted because it doesn't matter, this is a test we 1492 // are already done with that is clearing out. Then AfterOnLoadScripts 1493 // should finish quicker if that is who is calling us. 1494 return Promise.resolve(true); 1495 } 1496 1497 // If we're in the same process as the top-level XUL window, then 1498 // drawing that window will also update our layers, so no 1499 // synchronization is needed. 1500 // 1501 // NB: this is a test-harness optimization only, it must not 1502 // affect the validity of the tests. 1503 if (gBrowserIsRemote) { 1504 await SynchronizeForSnapshot(SYNC_DEFAULT); 1505 let promise = new Promise(resolve => { 1506 gUpdateCanvasPromiseResolver = resolve; 1507 }); 1508 sendAsyncMessage("reftest:InitCanvasWithSnapshot"); 1509 1510 gHaveCanvasSnapshot = await promise; 1511 return gHaveCanvasSnapshot; 1512 } 1513 1514 // For in-process browser, we have to make a synchronous request 1515 // here to make the above optimization valid, so that MozWaitPaint 1516 // events dispatched (synchronously) during painting are received 1517 // before we check the paint-wait counter. For out-of-process 1518 // browser though, it doesn't wrt correctness whether this request 1519 // is sync or async. 1520 let promise = new Promise(resolve => { 1521 gUpdateCanvasPromiseResolver = resolve; 1522 }); 1523 sendAsyncMessage("reftest:InitCanvasWithSnapshot"); 1524 1525 gHaveCanvasSnapshot = await promise; 1526 return Promise.resolve(gHaveCanvasSnapshot); 1527 } 1528 1529 function SendScriptResults(runtimeMs, error, results) { 1530 sendAsyncMessage("reftest:ScriptResults", { 1531 runtimeMs, 1532 error, 1533 results, 1534 }); 1535 } 1536 1537 function SendStartPrint(isPrintSelection, printRange) { 1538 sendAsyncMessage("reftest:StartPrint", { isPrintSelection, printRange }); 1539 } 1540 1541 function SendPrintResult(runtimeMs, status, fileName) { 1542 sendAsyncMessage("reftest:PrintResult", { 1543 runtimeMs, 1544 status, 1545 fileName, 1546 }); 1547 } 1548 1549 function SendExpectProcessCrash() { 1550 sendAsyncMessage("reftest:ExpectProcessCrash"); 1551 } 1552 1553 function SendTestDone(runtimeMs) { 1554 sendAsyncMessage("reftest:TestDone", { runtimeMs }); 1555 } 1556 1557 function roundTo(x, fraction) { 1558 return Math.round(x / fraction) * fraction; 1559 } 1560 1561 function elementDescription(element) { 1562 return ( 1563 "<" + 1564 element.localName + 1565 [].slice 1566 .call(element.attributes) 1567 .map(attr => ` ${attr.nodeName}="${attr.value}"`) 1568 .join("") + 1569 ">" 1570 ); 1571 } 1572 1573 async function SendUpdateCanvasForEvent(forURL, rectList, contentRootElement) { 1574 if (forURL != gCurrentURL) { 1575 LogInfo("SendUpdateCanvasForEvent called for previous document"); 1576 // This is a test we are already done with that is clearing out. 1577 // Don't do anything. 1578 return; 1579 } 1580 1581 var scale = docShell.browsingContext.fullZoom; 1582 1583 var rects = []; 1584 if (shouldSnapshotWholePage(contentRootElement)) { 1585 // See comments in SendInitCanvasWithSnapshot() re: the split 1586 // logic here. 1587 if (!gBrowserIsRemote) { 1588 sendSyncMessage("reftest:UpdateWholeCanvasForInvalidation"); 1589 } else { 1590 await SynchronizeForSnapshot(SYNC_ALLOW_DISABLE); 1591 let promise = new Promise(resolve => { 1592 gUpdateCanvasPromiseResolver = resolve; 1593 }); 1594 sendAsyncMessage("reftest:UpdateWholeCanvasForInvalidation"); 1595 await promise; 1596 } 1597 return; 1598 } 1599 1600 var message; 1601 1602 if (!windowUtils().isMozAfterPaintPending) { 1603 // Webrender doesn't have invalidation, and animations on the compositor 1604 // don't invoke any MozAfterEvent which means we have no invalidated 1605 // rect so we just invalidate the whole screen once we don't have 1606 // anymore paints pending. This will force the snapshot. 1607 1608 LogInfo("Sending update whole canvas for invalidation"); 1609 message = "reftest:UpdateWholeCanvasForInvalidation"; 1610 } else { 1611 LogInfo("SendUpdateCanvasForEvent with " + rectList.length + " rects"); 1612 for (var i = 0; i < rectList.length; ++i) { 1613 var r = rectList[i]; 1614 // Set left/top/right/bottom to "device pixel" boundaries 1615 var left = Math.floor(roundTo(r.left * scale, 0.001)); 1616 var top = Math.floor(roundTo(r.top * scale, 0.001)); 1617 var right = Math.ceil(roundTo(r.right * scale, 0.001)); 1618 var bottom = Math.ceil(roundTo(r.bottom * scale, 0.001)); 1619 LogInfo("Rect: " + left + " " + top + " " + right + " " + bottom); 1620 1621 rects.push({ left, top, right, bottom }); 1622 } 1623 1624 message = "reftest:UpdateCanvasForInvalidation"; 1625 } 1626 1627 // See comments in SendInitCanvasWithSnapshot() re: the split 1628 // logic here. 1629 if (!gBrowserIsRemote) { 1630 sendSyncMessage(message, { rects }); 1631 } else { 1632 await SynchronizeForSnapshot(SYNC_ALLOW_DISABLE); 1633 let promise = new Promise(resolve => { 1634 gUpdateCanvasPromiseResolver = resolve; 1635 }); 1636 sendAsyncMessage(message, { rects }); 1637 await promise; 1638 } 1639 } 1640 1641 if (content.document.readyState == "complete") { 1642 // load event has already fired for content, get started 1643 OnInitialLoad(); 1644 } else { 1645 addEventListener("load", OnInitialLoad, true); 1646 }