Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/layout/tools/reftest/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 52 kB image not shown  

Quelle  reftest-content.js   Sprache: JAVA

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


/* eslint-env mozilla/frame-script */

const XHTML_NS = "http://www.w3.org/1999/xhtml";

const DEBUG_CONTRACTID = "@mozilla.org/xpcom/debug;1";
const PRINTSETTINGS_CONTRACTID = "@mozilla.org/gfx/printsettings-service;1";
const NS_OBSERVER_SERVICE_CONTRACTID = "@mozilla.org/observer-service;1";
const NS_GFXINFO_CONTRACTID = "@mozilla.org/gfx/info;1";
const IO_SERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";

// "<!--CLEAR-->"
const BLANK_URL_FOR_CLEARING =
  "data:text/html;charset=UTF-8,%3C%21%2D%2DCLEAR%2D%2D%3E";

const { setTimeout, clearTimeout } = ChromeUtils.importESModule(
  "resource://gre/modules/Timer.sys.mjs"
);
const { onSpellCheck } = ChromeUtils.importESModule(
  "resource://reftest/AsyncSpellCheckTestHelper.sys.mjs"
);

// This will load chrome Custom Elements inside chrome documents:
ChromeUtils.importESModule(
  "resource://gre/modules/CustomElementsListener.sys.mjs"
);

var gBrowserIsRemote;
var gHaveCanvasSnapshot = false;
var gCurrentURL;
var gCurrentURLRecordResults;
var gCurrentURLTargetType;
var gCurrentTestType;
var gTimeoutHook = null;
var gFailureTimeout = null;
var gFailureReason;
var gAssertionCount = 0;
var gUpdateCanvasPromiseResolver = null;

var gDebug;
var gVerbose = false;

var gCurrentTestStartTime;
var gClearingForAssertionCheck = false;

const TYPE_LOAD = "load"// test without a reference (just test that it does
// not assert, crash, hang, or leak)
const TYPE_SCRIPT = "script"// test contains individual test results
const TYPE_PRINT = "print"// test and reference will be printed to PDF's and
// compared structurally

// keep this in sync with globals.sys.mjs
const URL_TARGET_TYPE_TEST = 0; // first url
const URL_TARGET_TYPE_REFERENCE = 1; // second url, if any

function webNavigation() {
  return docShell.QueryInterface(Ci.nsIWebNavigation);
}

function webProgress() {
  return docShell
    .QueryInterface(Ci.nsIInterfaceRequestor)
    .getInterface(Ci.nsIWebProgress);
}

function windowUtilsForWindow(w) {
  return w.windowUtils;
}

function windowUtils() {
  return windowUtilsForWindow(content);
}

function IDForEventTarget(event) {
  try {
    return "'" + event.target.getAttribute("id") + "'";
  } catch (ex) {
    return "";
  }
}

var progressListener = {
  onStateChange(webprogress, request, flags) {
    let uri;
    try {
      request.QueryInterface(Ci.nsIChannel);
      uri = request.originalURI.spec;
    } catch (ex) {
      return;
    }
    const WPL = Ci.nsIWebProgressListener;
    const endFlags =
      WPL.STATE_STOP | WPL.STATE_IS_WINDOW | WPL.STATE_IS_NETWORK;
    if ((flags & endFlags) == endFlags) {
      OnDocumentLoad(uri);
    }
  },
  QueryInterface: ChromeUtils.generateQI([
    "nsIWebProgressListener",
    "nsISupportsWeakReference",
  ]),
};

function OnInitialLoad() {
  removeEventListener("load", OnInitialLoad, true);

  gDebug = Cc[DEBUG_CONTRACTID].getService(Ci.nsIDebug2);
  if (gDebug.isDebugBuild) {
    gAssertionCount = gDebug.assertionCount;
  }
  gVerbose = !!Services.env.get("MOZ_REFTEST_VERBOSE");

  RegisterMessageListeners();

  var initInfo = SendContentReady();
  gBrowserIsRemote = initInfo.remote;

  webProgress().addProgressListener(
    progressListener,
    Ci.nsIWebProgress.NOTIFY_STATE_WINDOW
  );

  LogInfo("Using browser remote=" + gBrowserIsRemote + "\n");
}

function SetFailureTimeout(cb, timeout, uri) {
  var targetTime = Date.now() + timeout;

  var wrapper = function () {
    // Timeouts can fire prematurely in some cases (e.g. in chaos mode). If this
    // happens, set another timeout for the remaining time.
    let remainingMs = targetTime - Date.now();
    if (remainingMs > 0) {
      SetFailureTimeout(cb, remainingMs);
    } else {
      cb();
    }
  };

  // Once OnDocumentLoad is called to handle the 'load' event it will update
  // this error message to reflect what stage of the processing it has reached
  // as it advances to each stage in turn.
  gFailureReason =
    "timed out after " + timeout + " ms waiting for 'load' event for " + uri;
  gFailureTimeout = setTimeout(wrapper, timeout);
}

function StartTestURI(type, uri, uriTargetType, timeout) {
  // The GC is only able to clean up compartments after the CC runs. Since
  // the JS ref tests disable the normal browser chrome and do not otherwise
  // create substatial DOM garbage, the CC tends not to run enough normally.
  windowUtils().runNextCollectorTimer();

  gCurrentTestType = type;
  gCurrentURL = uri;
  gCurrentURLTargetType = uriTargetType;
  gCurrentURLRecordResults = 0;

  gCurrentTestStartTime = Date.now();
  if (gFailureTimeout != null) {
    SendException("program error managing timeouts\n");
  }
  SetFailureTimeout(LoadFailed, timeout, uri);

  LoadURI(gCurrentURL);
}

function setupTextZoom(contentRootElement) {
  if (
    !contentRootElement ||
    !contentRootElement.hasAttribute("reftest-text-zoom")
  ) {
    return;
  }
  docShell.browsingContext.textZoom =
    contentRootElement.getAttribute("reftest-text-zoom");
}

function setupFullZoom(contentRootElement) {
  if (!contentRootElement || !contentRootElement.hasAttribute("reftest-zoom")) {
    return;
  }
  docShell.browsingContext.fullZoom =
    contentRootElement.getAttribute("reftest-zoom");
}

function resetZoomAndTextZoom() {
  docShell.browsingContext.fullZoom = 1.0;
  docShell.browsingContext.textZoom = 1.0;
}

function doPrintMode(contentRootElement) {
  // use getAttribute because className works differently in HTML and SVG
  if (contentRootElement && contentRootElement.hasAttribute("class")) {
    var classList = contentRootElement.getAttribute("class").split(/\s+/);
    if (classList.includes("reftest-print")) {
      SendException("reftest-print is obsolete, use reftest-paged instead");
      return false;
    }
    return classList.includes("reftest-paged");
  }
  return false;
}

function setupPrintMode(contentRootElement) {
  var PSSVC = Cc[PRINTSETTINGS_CONTRACTID].getService(
    Ci.nsIPrintSettingsService
  );
  var ps = PSSVC.createNewPrintSettings();
  ps.paperWidth = 5;
  ps.paperHeight = 3;

  // Override any os-specific unwriteable margins
  ps.unwriteableMarginTop = 0;
  ps.unwriteableMarginLeft = 0;
  ps.unwriteableMarginBottom = 0;
  ps.unwriteableMarginRight = 0;

  ps.headerStrLeft = "";
  ps.headerStrCenter = "";
  ps.headerStrRight = "";
  ps.footerStrLeft = "";
  ps.footerStrCenter = "";
  ps.footerStrRight = "";

  const printBackgrounds = (() => {
    const attr = contentRootElement.getAttribute("reftest-paged-backgrounds");
    return !attr || attr != "false";
  })();
  ps.printBGColors = printBackgrounds;
  ps.printBGImages = printBackgrounds;

  docShell.docViewer.setPageModeForTesting(/* aPageMode */ true, ps);
}

// Message the parent process to ask it to print the current page to a PDF file.
function printToPdf() {
  let currentDoc = content.document;
  let isPrintSelection = false;
  let printRange = "";

  if (currentDoc) {
    let contentRootElement = currentDoc.documentElement;
    printRange = contentRootElement.getAttribute("reftest-print-range") || "";
  }

  if (printRange) {
    if (printRange === "selection") {
      isPrintSelection = true;
    } else if (
      !printRange.split(",").every(range => /^[1-9]\d*-[1-9]\d*$/.test(range))
    ) {
      SendException("invalid value for reftest-print-range");
      return;
    }
  }

  SendStartPrint(isPrintSelection, printRange);
}

function attrOrDefault(element, attr, def) {
  return element.hasAttribute(attr) ? Number(element.getAttribute(attr)) : def;
}

function setupViewport(contentRootElement) {
  if (!contentRootElement) {
    return;
  }

  var sw = attrOrDefault(contentRootElement, "reftest-scrollport-w", 0);
  var sh = attrOrDefault(contentRootElement, "reftest-scrollport-h", 0);
  if (sw !== 0 || sh !== 0) {
    LogInfo("Setting viewport to  + sw + ", h=" + sh + ">");
    windowUtils().setVisualViewportSize(sw, sh);
  }

  var res = attrOrDefault(contentRootElement, "reftest-resolution", 1);
  if (res !== 1) {
    LogInfo("Setting resolution to " + res);
    windowUtils().setResolutionAndScaleTo(res);
  }

  // XXX support viewconfig when needed
}

function setupDisplayport() {
  let promise = content.windowGlobalChild
    .getActor("ReftestFission")
    .SetupDisplayportRoot();
  return promise.then(
    function (result) {
      for (let errorString of result.errorStrings) {
        LogError(errorString);
      }
      for (let infoString of result.infoStrings) {
        LogInfo(infoString);
      }
    },
    function (reason) {
      LogError("SetupDisplayportRoot returned promise rejected: " + reason);
    }
  );
}

// Returns whether any offsets were updated
function setupAsyncScrollOffsets(options) {
  let currentDoc = content.document;
  let contentRootElement = currentDoc ? currentDoc.documentElement : null;

  if (
    !contentRootElement ||
    !contentRootElement.hasAttribute("reftest-async-scroll")
  ) {
    return Promise.resolve(false);
  }

  let allowFailure = options.allowFailure;
  let promise = content.windowGlobalChild
    .getActor("ReftestFission")
    .sendQuery("SetupAsyncScrollOffsets", { allowFailure });
  return promise.then(
    function (result) {
      for (let errorString of result.errorStrings) {
        LogError(errorString);
      }
      for (let infoString of result.infoStrings) {
        LogInfo(infoString);
      }
      return result.updatedAny;
    },
    function (reason) {
      LogError(
        "SetupAsyncScrollOffsets SendQuery to parent promise rejected: " +
          reason
      );
      return false;
    }
  );
}

function setupAsyncZoom(options) {
  var currentDoc = content.document;
  var contentRootElement = currentDoc ? currentDoc.documentElement : null;

  if (
    !contentRootElement ||
    !contentRootElement.hasAttribute("reftest-async-zoom")
  ) {
    return false;
  }

  var zoom = attrOrDefault(contentRootElement, "reftest-async-zoom", 1);
  if (zoom != 1) {
    try {
      windowUtils().setAsyncZoom(contentRootElement, zoom);
      return true;
    } catch (e) {
      if (!options.allowFailure) {
        throw e;
      }
    }
  }
  return false;
}

function resetDisplayportAndViewport() {
  // XXX currently the displayport configuration lives on the
  // presshell and so is "reset" on nav when we get a new presshell.
}

function shouldWaitForPendingPaints() {
  // if gHaveCanvasSnapshot is false, we're not taking snapshots so
  // there is no need to wait for pending paints to be flushed.
  return gHaveCanvasSnapshot && windowUtils().isMozAfterPaintPending;
}

function shouldWaitForReftestWaitRemoval(contentRootElement) {
  // use getAttribute because className works differently in HTML and SVG
  return (
    contentRootElement &&
    contentRootElement.hasAttribute("class") &&
    contentRootElement
      .getAttribute("class")
      .split(/\s+/)
      .includes("reftest-wait")
  );
}

function shouldSnapshotWholePage(contentRootElement) {
  // use getAttribute because className works differently in HTML and SVG
  return (
    contentRootElement &&
    contentRootElement.hasAttribute("class") &&
    contentRootElement
      .getAttribute("class")
      .split(/\s+/)
      .includes("reftest-snapshot-all")
  );
}

function shouldNotFlush(contentRootElement) {
  // use getAttribute because className works differently in HTML and SVG
  return (
    contentRootElement &&
    contentRootElement.hasAttribute("class") &&
    contentRootElement
      .getAttribute("class")
      .split(/\s+/)
      .includes("reftest-no-flush")
  );
}

function getNoPaintElements(contentRootElement) {
  return contentRootElement.getElementsByClassName("reftest-no-paint");
}
function getNoDisplayListElements(contentRootElement) {
  return contentRootElement.getElementsByClassName("reftest-no-display-list");
}
function getDisplayListElements(contentRootElement) {
  return contentRootElement.getElementsByClassName("reftest-display-list");
}

function getOpaqueLayerElements(contentRootElement) {
  return contentRootElement.getElementsByClassName("reftest-opaque-layer");
}

function getAssignedLayerMap(contentRootElement) {
  var layerNameToElementsMap = {};
  var elements = contentRootElement.querySelectorAll(
    "[reftest-assigned-layer]"
  );
  for (var i = 0; i < elements.length; ++i) {
    var element = elements[i];
    var layerName = element.getAttribute("reftest-assigned-layer");
    if (!(layerName in layerNameToElementsMap)) {
      layerNameToElementsMap[layerName] = [];
    }
    layerNameToElementsMap[layerName].push(element);
  }
  return layerNameToElementsMap;
}

const FlushMode = {
  ALL: 0,
  IGNORE_THROTTLED_ANIMATIONS: 1,
};

// Initial state. When the document has loaded and all MozAfterPaint events and
// all explicit paint waits are flushed, we can fire the MozReftestInvalidate
// event and move to the next state.
const STATE_WAITING_TO_FIRE_INVALIDATE_EVENT = 0;
// When reftest-wait has been removed from the root element, we can move to the
// next state.
const STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL = 1;
// When spell checking is done on all spell-checked elements, we can move to the
// next state.
const STATE_WAITING_FOR_SPELL_CHECKS = 2;
// When any pending compositor-side repaint requests have been flushed, we can
// move to the next state.
const STATE_WAITING_FOR_APZ_FLUSH = 3;
// When all MozAfterPaint events and all explicit paint waits are flushed, we're
// done and can move to the COMPLETED state.
const STATE_WAITING_TO_FINISH = 4;
const STATE_COMPLETED = 5;

async function FlushRendering(aFlushMode) {
  let browsingContext = content.docShell.browsingContext;
  let ignoreThrottledAnimations =
    aFlushMode === FlushMode.IGNORE_THROTTLED_ANIMATIONS;
  // Ensure the refresh driver ticks at least once, this ensures some
  // preference changes take effect.
  let needsAnimationFrame = IsSnapshottableTestType();
  try {
    let result = await content.windowGlobalChild
      .getActor("ReftestFission")
      .sendQuery("FlushRendering", {
        browsingContext,
        ignoreThrottledAnimations,
        needsAnimationFrame,
      });
    for (let errorString of result.errorStrings) {
      LogError(errorString);
    }
    for (let warningString of result.warningStrings) {
      LogWarning(warningString);
    }
    for (let infoString of result.infoStrings) {
      LogInfo(infoString);
    }
  } catch (reason) {
    // We expect actors to go away causing sendQuery's to fail, so
    // just note it.
    LogInfo("FlushRendering sendQuery to parent rejected: " + reason);
  }
}

function WaitForTestEnd(
  contentRootElement,
  inPrintMode,
  spellCheckedElements,
  forURL
) {
  // WaitForTestEnd works via the MakeProgress function below. It is responsible for
  // moving through the states listed above and calling FlushRendering. We also listen
  // for a number of events, the most important of which is the AfterPaintListener,
  // which is responsible for updating the canvas after paints. In a fission world
  // FlushRendering and updating the canvas must necessarily be async operations.
  // During these async operations we want to wait for them to finish and we don't
  // want to try to do anything else (what would we even want to do while only some of
  // the processes involved have flushed layout or updated their layer trees?). So
  // we call OperationInProgress whenever we are about to go back to the event loop
  // during one of these calls, and OperationCompleted when it finishes. This prevents
  // anything else from running while we wait and getting us into a confused state. We
  // then record anything that happens while we are waiting to make sure that the
  // right actions are triggered. The possible actions are basically calling
  // MakeProgress from a setTimeout, and updating the canvas for an after paint event.
  // The after paint listener just stashes the rects and we update them after a
  // completed MakeProgress call. This is handled by
  // HandlePendingTasksAfterMakeProgress, which also waits for any pending after paint
  // events. The general sequence of events is:
  //   - MakeProgress
  //   - HandlePendingTasksAfterMakeProgress
  //     - wait for after paint event if one is pending
  //     - update canvas for after paint events we have received
  //   - MakeProgress
  //   etc

  function CheckForLivenessOfContentRootElement() {
    if (contentRootElement && Cu.isDeadWrapper(contentRootElement)) {
      contentRootElement = null;
    }
  }

  var setTimeoutCallMakeProgressWhenComplete = false;

  var operationInProgress = false;
  function OperationInProgress() {
    if (operationInProgress) {
      LogWarning("Nesting atomic operations?");
    }
    operationInProgress = true;
  }
  function OperationCompleted() {
    if (!operationInProgress) {
      LogWarning("Mismatched OperationInProgress/OperationCompleted calls?");
    }
    operationInProgress = false;
    if (setTimeoutCallMakeProgressWhenComplete) {
      setTimeoutCallMakeProgressWhenComplete = false;
      setTimeout(CallMakeProgress, 0);
    }
  }
  function AssertNoOperationInProgress() {
    if (operationInProgress) {
      LogWarning("AssertNoOperationInProgress but operationInProgress");
    }
  }

  var updateCanvasPending = false;
  var updateCanvasRects = [];

  var currentDoc = content.document;
  var state = STATE_WAITING_TO_FIRE_INVALIDATE_EVENT;

  var setTimeoutMakeProgressPending = false;

  function CallSetTimeoutMakeProgress() {
    if (setTimeoutMakeProgressPending) {
      return;
    }
    setTimeoutMakeProgressPending = true;
    setTimeout(CallMakeProgress, 0);
  }

  // This should only ever be called from a timeout.
  function CallMakeProgress() {
    if (operationInProgress) {
      setTimeoutCallMakeProgressWhenComplete = true;
      return;
    }
    setTimeoutMakeProgressPending = false;
    MakeProgress();
  }

  var waitingForAnAfterPaint = false;

  // Updates the canvas if there are pending updates for it. Checks if we
  // need to call MakeProgress.
  function HandlePendingTasksAfterMakeProgress() {
    AssertNoOperationInProgress();

    if (
      (state == STATE_WAITING_TO_FIRE_INVALIDATE_EVENT ||
        state == STATE_WAITING_TO_FINISH) &&
      shouldWaitForPendingPaints()
    ) {
      LogInfo(
        "HandlePendingTasksAfterMakeProgress waiting for a MozAfterPaint"
      );
      // We are in a state where we wait for MozAfterPaint to clear and a
      // MozAfterPaint event is pending, give it a chance to fire, but don't
      // let anything else run.
      waitingForAnAfterPaint = true;
      OperationInProgress();
      return;
    }

    if (updateCanvasPending) {
      LogInfo("HandlePendingTasksAfterMakeProgress updating canvas");
      updateCanvasPending = false;
      let rects = updateCanvasRects;
      updateCanvasRects = [];
      OperationInProgress();
      CheckForLivenessOfContentRootElement();
      let promise = SendUpdateCanvasForEvent(forURL, rects, contentRootElement);
      promise.then(function () {
        OperationCompleted();
        // After paint events are fired immediately after a paint (one
        // of the things that can call us). Don't confuse ourselves by
        // firing synchronously if we triggered the paint ourselves.
        CallSetTimeoutMakeProgress();
      });
    }
  }

  // true if rectA contains rectB
  function Contains(rectA, rectB) {
    return (
      rectA.left <= rectB.left &&
      rectB.right <= rectA.right &&
      rectA.top <= rectB.top &&
      rectB.bottom <= rectA.bottom
    );
  }
  // true if some rect in rectList contains rect
  function ContainedIn(rectList, rect) {
    for (let i = 0; i < rectList.length; ++i) {
      if (Contains(rectList[i], rect)) {
        return true;
      }
    }
    return false;
  }

  function AfterPaintListener(event) {
    LogInfo("AfterPaintListener in " + event.target.document.location.href);
    if (event.target.document != currentDoc) {
      // ignore paint events for subframes or old documents in the window.
      // Invalidation in subframes will cause invalidation in the toplevel document anyway.
      return;
    }

    updateCanvasPending = true;
    for (let r of event.clientRects) {
      if (ContainedIn(updateCanvasRects, r)) {
        continue;
      }

      // Copy the rect; it's content and we are chrome, which means if the
      // document goes away (and it can in some crashtests) our reference
      // to it will be turned into a dead wrapper that we can't acccess.
      updateCanvasRects.push({
        left: r.left,
        top: r.top,
        right: r.right,
        bottom: r.bottom,
      });
    }

    if (waitingForAnAfterPaint) {
      waitingForAnAfterPaint = false;
      OperationCompleted();
    }

    if (!operationInProgress) {
      HandlePendingTasksAfterMakeProgress();
    }
    // Otherwise we know that eventually after the operation finishes we
    // will get a MakeProgress and/or HandlePendingTasksAfterMakeProgress
    // call, so we don't need to do anything.
  }

  function FromChildAfterPaintListener(event) {
    LogInfo(
      "FromChildAfterPaintListener from " + event.detail.originalTargetUri
    );

    updateCanvasPending = true;
    for (let r of event.detail.rects) {
      if (ContainedIn(updateCanvasRects, r)) {
        continue;
      }

      // Copy the rect; it's content and we are chrome, which means if the
      // document goes away (and it can in some crashtests) our reference
      // to it will be turned into a dead wrapper that we can't acccess.
      updateCanvasRects.push({
        left: r.left,
        top: r.top,
        right: r.right,
        bottom: r.bottom,
      });
    }

    if (!operationInProgress) {
      HandlePendingTasksAfterMakeProgress();
    }
    // Otherwise we know that eventually after the operation finishes we
    // will get a MakeProgress and/or HandlePendingTasksAfterMakeProgress
    // call, so we don't need to do anything.
  }

  let attrModifiedObserver;
  function AttrModifiedListener() {
    LogInfo("AttrModifiedListener fired");
    // Wait for the next return-to-event-loop before continuing --- for
    // example, the attribute may have been modified in an subdocument's
    // load event handler, in which case we need load event processing
    // to complete and unsuppress painting before we check isMozAfterPaintPending.
    CallSetTimeoutMakeProgress();
  }

  function RemoveListeners() {
    // OK, we can end the test now.
    removeEventListener("MozAfterPaint", AfterPaintListener, false);
    removeEventListener(
      "Reftest:MozAfterPaintFromChild",
      FromChildAfterPaintListener,
      false
    );
    CheckForLivenessOfContentRootElement();
    if (attrModifiedObserver) {
      if (!Cu.isDeadWrapper(attrModifiedObserver)) {
        attrModifiedObserver.disconnect();
      }
      attrModifiedObserver = null;
    }
    gTimeoutHook = null;
    // Make sure we're in the COMPLETED state just in case
    // (this may be called via the test-timeout hook)
    state = STATE_COMPLETED;
  }

  // Everything that could cause shouldWaitForXXX() to
  // change from returning true to returning false is monitored via some kind
  // of event listener which eventually calls this function.
  function MakeProgress() {
    if (state >= STATE_COMPLETED) {
      LogInfo("MakeProgress: STATE_COMPLETED");
      return;
    }

    LogInfo("MakeProgress");

    // We don't need to flush styles any more when we are in the state
    // after reftest-wait has removed.
    OperationInProgress();
    let promise = Promise.resolve(undefined);
    if (state != STATE_WAITING_TO_FINISH) {
      // If we are waiting for the MozReftestInvalidate event we don't want
      // to flush throttled animations. Flushing throttled animations can
      // continue to cause new MozAfterPaint events even when all the
      // rendering we're concerned about should have ceased. Since
      // MozReftestInvalidate won't be sent until we finish waiting for all
      // MozAfterPaint events, we should avoid flushing throttled animations
      // here or else we'll never leave this state.
      let flushMode =
        state === STATE_WAITING_TO_FIRE_INVALIDATE_EVENT
          ? FlushMode.IGNORE_THROTTLED_ANIMATIONS
          : FlushMode.ALL;
      promise = FlushRendering(flushMode);
    }
    promise.then(function () {
      OperationCompleted();
      MakeProgress2();
      // If there is an operation in progress then we know there will be
      // a MakeProgress call is will happen after it finishes.
      if (!operationInProgress) {
        HandlePendingTasksAfterMakeProgress();
      }
    });
  }

  // eslint-disable-next-line complexity
  function MakeProgress2() {
    switch (state) {
      case STATE_WAITING_TO_FIRE_INVALIDATE_EVENT: {
        LogInfo("MakeProgress: STATE_WAITING_TO_FIRE_INVALIDATE_EVENT");
        if (shouldWaitForPendingPaints() || updateCanvasPending) {
          gFailureReason =
            "timed out waiting for pending paint count to reach zero";
          if (shouldWaitForPendingPaints()) {
            gFailureReason += " (waiting for MozAfterPaint)";
            LogInfo("MakeProgress: waiting for MozAfterPaint");
          }
          if (updateCanvasPending) {
            gFailureReason += " (waiting for updateCanvasPending)";
            LogInfo("MakeProgress: waiting for updateCanvasPending");
          }
          return;
        }

        state = STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL;
        CheckForLivenessOfContentRootElement();
        var hasReftestWait =
          shouldWaitForReftestWaitRemoval(contentRootElement);
        // Notify the test document that now is a good time to test some invalidation
        LogInfo("MakeProgress: dispatching MozReftestInvalidate");
        if (contentRootElement) {
          let elements = getNoPaintElements(contentRootElement);
          for (let i = 0; i < elements.length; ++i) {
            windowUtils().checkAndClearPaintedState(elements[i]);
          }
          elements = getNoDisplayListElements(contentRootElement);
          for (let i = 0; i < elements.length; ++i) {
            windowUtils().checkAndClearDisplayListState(elements[i]);
          }
          elements = getDisplayListElements(contentRootElement);
          for (let i = 0; i < elements.length; ++i) {
            windowUtils().checkAndClearDisplayListState(elements[i]);
          }
          var notification = content.document.createEvent("Events");
          notification.initEvent("MozReftestInvalidate"truefalse);
          contentRootElement.dispatchEvent(notification);
        } else {
          LogInfo(
            "MakeProgress: couldn't send MozReftestInvalidate event because content root element does not exist"
          );
        }

        CheckForLivenessOfContentRootElement();
        if (!inPrintMode && doPrintMode(contentRootElement)) {
          LogInfo("MakeProgress: setting up print mode");
          setupPrintMode(contentRootElement);
        }

        CheckForLivenessOfContentRootElement();
        if (
          hasReftestWait &&
          !shouldWaitForReftestWaitRemoval(contentRootElement)
        ) {
          // MozReftestInvalidate handler removed reftest-wait.
          // We expect something to have been invalidated...
          OperationInProgress();
          let promise = FlushRendering(FlushMode.ALL);
          promise.then(function () {
            OperationCompleted();
            if (!updateCanvasPending && !shouldWaitForPendingPaints()) {
              LogWarning("MozInvalidateEvent didn't invalidate");
            }
            MakeProgress();
          });
          return;
        }
        // Try next state
        MakeProgress();
        return;
      }

      case STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL:
        LogInfo("MakeProgress: STATE_WAITING_FOR_REFTEST_WAIT_REMOVAL");
        CheckForLivenessOfContentRootElement();
        if (shouldWaitForReftestWaitRemoval(contentRootElement)) {
          gFailureReason = "timed out waiting for reftest-wait to be removed";
          LogInfo("MakeProgress: waiting for reftest-wait to be removed");
          return;
        }

        if (shouldNotFlush(contentRootElement)) {
          // If reftest-no-flush is specified, we need to set
          // updateCanvasPending explicitly to take the latest snapshot
          // since animation changes on the compositor thread don't invoke
          // any MozAfterPaint events at all.
          // NOTE: We don't add any rects to updateCanvasRects here since
          // SendUpdateCanvasForEvent() will handle this case properly
          // without any rects.
          updateCanvasPending = true;
        }
        // Try next state
        state = STATE_WAITING_FOR_SPELL_CHECKS;
        MakeProgress();
        return;

      case STATE_WAITING_FOR_SPELL_CHECKS:
        LogInfo("MakeProgress: STATE_WAITING_FOR_SPELL_CHECKS");
        if (numPendingSpellChecks) {
          gFailureReason = "timed out waiting for spell checks to end";
          LogInfo("MakeProgress: waiting for spell checks to end");
          return;
        }

        state = STATE_WAITING_FOR_APZ_FLUSH;
        LogInfo("MakeProgress: STATE_WAITING_FOR_APZ_FLUSH");
        gFailureReason = "timed out waiting for APZ flush to complete";

        var flushWaiter = function (aSubject, aTopic) {
          if (aTopic) {
            LogInfo("MakeProgress: apz-repaints-flushed fired");
          }
          Services.obs.removeObserver(flushWaiter, "apz-repaints-flushed");
          state = STATE_WAITING_TO_FINISH;
          if (operationInProgress) {
            CallSetTimeoutMakeProgress();
          } else {
            MakeProgress();
          }
        };
        Services.obs.addObserver(flushWaiter, "apz-repaints-flushed");

        var willSnapshot = IsSnapshottableTestType();
        CheckForLivenessOfContentRootElement();
        var noFlush = !shouldNotFlush(contentRootElement);
        if (noFlush && willSnapshot && windowUtils().flushApzRepaints()) {
          LogInfo("MakeProgress: done requesting APZ flush");
        } else {
          LogInfo("MakeProgress: APZ flush not required");
          flushWaiter(nullnullnull);
        }
        return;

      case STATE_WAITING_FOR_APZ_FLUSH:
        LogInfo("MakeProgress: STATE_WAITING_FOR_APZ_FLUSH");
        // Nothing to do here; once we get the apz-repaints-flushed event
        // we will go to STATE_WAITING_TO_FINISH
        return;

      case STATE_WAITING_TO_FINISH:
        LogInfo("MakeProgress: STATE_WAITING_TO_FINISH");
        if (shouldWaitForPendingPaints() || updateCanvasPending) {
          gFailureReason =
            "timed out waiting for pending paint count to " +
            "reach zero (after reftest-wait removed and switch to print mode)";
          if (shouldWaitForPendingPaints()) {
            gFailureReason += " (waiting for MozAfterPaint)";
            LogInfo("MakeProgress: waiting for MozAfterPaint");
          }
          if (updateCanvasPending) {
            gFailureReason += " (waiting for updateCanvasPending)";
            LogInfo("MakeProgress: waiting for updateCanvasPending");
          }
          return;
        }
        CheckForLivenessOfContentRootElement();
        if (contentRootElement) {
          let elements = getNoPaintElements(contentRootElement);
          for (let i = 0; i < elements.length; ++i) {
            if (windowUtils().checkAndClearPaintedState(elements[i])) {
              SendFailedNoPaint();
            }
          }
          // We only support retained display lists in the content process
          // right now, so don't fail reftest-no-display-list tests when
          // we don't have e10s.
          if (gBrowserIsRemote) {
            elements = getNoDisplayListElements(contentRootElement);
            for (let i = 0; i < elements.length; ++i) {
              if (windowUtils().checkAndClearDisplayListState(elements[i])) {
                SendFailedNoDisplayList();
              }
            }
            elements = getDisplayListElements(contentRootElement);
            for (let i = 0; i < elements.length; ++i) {
              if (!windowUtils().checkAndClearDisplayListState(elements[i])) {
                SendFailedDisplayList();
              }
            }
          }
        }

        if (!IsSnapshottableTestType()) {
          // If we're not snapshotting the test, at least do a sync round-trip
          // to the compositor to ensure that all the rendering messages
          // related to this test get processed. Otherwise problems triggered
          // by this test may only manifest as failures in a later test.
          LogInfo("MakeProgress: Doing sync flush to compositor");
          gFailureReason = "timed out while waiting for sync compositor flush";
          windowUtils().syncFlushCompositor();
        }

        LogInfo("MakeProgress: Completed");
        state = STATE_COMPLETED;
        gFailureReason = "timed out while taking snapshot (bug in harness?)";
        RemoveListeners();
        CheckForLivenessOfContentRootElement();
        CheckForProcessCrashExpectation(contentRootElement);
        setTimeout(RecordResult, 0, forURL);
    }
  }

  LogInfo("WaitForTestEnd: Adding listeners");
  addEventListener("MozAfterPaint", AfterPaintListener, false);
  addEventListener(
    "Reftest:MozAfterPaintFromChild",
    FromChildAfterPaintListener,
    false
  );

  // If contentRootElement is null then shouldWaitForReftestWaitRemoval will
  // always return false so we don't need a listener anyway
  CheckForLivenessOfContentRootElement();
  if (contentRootElement?.hasAttribute("class")) {
    attrModifiedObserver =
      // ownerGlobal doesn't exist in content windows.
      // eslint-disable-next-line mozilla/use-ownerGlobal
      new contentRootElement.ownerDocument.defaultView.MutationObserver(
        AttrModifiedListener
      );
    attrModifiedObserver.observe(contentRootElement, { attributes: true });
  }
  gTimeoutHook = RemoveListeners;

  // Listen for spell checks on spell-checked elements.
  var numPendingSpellChecks = spellCheckedElements.length;
  function decNumPendingSpellChecks() {
    --numPendingSpellChecks;
    if (operationInProgress) {
      CallSetTimeoutMakeProgress();
    } else {
      MakeProgress();
    }
  }
  for (let editable of spellCheckedElements) {
    try {
      onSpellCheck(editable, decNumPendingSpellChecks);
    } catch (err) {
      // The element may not have an editor, so ignore it.
      setTimeout(decNumPendingSpellChecks, 0);
    }
  }

  // Take a full snapshot now that all our listeners are set up. This
  // ensures it's impossible for us to miss updates between taking the snapshot
  // and adding our listeners.
  OperationInProgress();
  let promise = SendInitCanvasWithSnapshot(forURL);
  promise.then(function () {
    OperationCompleted();
    MakeProgress();
  });
}

async function OnDocumentLoad(uri) {
  if (gClearingForAssertionCheck) {
    if (uri == BLANK_URL_FOR_CLEARING) {
      DoAssertionCheck();
      return;
    }

    // It's likely the previous test document reloads itself and causes the
    // attempt of loading blank page fails. In this case we should retry
    // loading the blank page.
    LogInfo("Retry loading a blank page");
    setTimeout(LoadURI, 0, BLANK_URL_FOR_CLEARING);
    return;
  }

  if (uri != gCurrentURL) {
    LogInfo("OnDocumentLoad fired for previous document");
    // Ignore load events for previous documents.
    return;
  }

  var currentDoc = content && content.document;

  // Collect all editable, spell-checked elements.  It may be the case that
  // not all the elements that match this selector will be spell checked: for
  // example, a textarea without a spellcheck attribute may have a parent with
  // spellcheck=false, or script may set spellcheck=false on an element whose
  // markup sets it to true.  But that's OK since onSpellCheck detects the
  // absence of spell checking, too.
  var querySelector =
    '*[class~="spell-checked"],' +
    'textarea:not([spellcheck="false"]),' +
    'input[spellcheck]:-moz-any([spellcheck=""],[spellcheck="true"]),' +
    '*[contenteditable]:-moz-any([contenteditable=""],[contenteditable="true"])';
  var spellCheckedElements = currentDoc
    ? currentDoc.querySelectorAll(querySelector)
    : [];

  var contentRootElement = currentDoc ? currentDoc.documentElement : null;
  currentDoc = null;
  setupFullZoom(contentRootElement);
  setupTextZoom(contentRootElement);
  setupViewport(contentRootElement);
  await setupDisplayport(contentRootElement);
  var inPrintMode = false;

  async function AfterOnLoadScripts() {
    // Regrab the root element, because the document may have changed.
    var contentRootElement = content.document
      ? content.document.documentElement
      : null;

    // Flush the document in case it got modified in a load event handler.
    await FlushRendering(FlushMode.ALL);

    // Take a snapshot now.
    let painted = await SendInitCanvasWithSnapshot(uri);

    if (contentRootElement && Cu.isDeadWrapper(contentRootElement)) {
      contentRootElement = null;
    }

    if (
      (!inPrintMode && doPrintMode(contentRootElement)) ||
      // If we didn't force a paint above, in
      // InitCurrentCanvasWithSnapshot, so we should wait for a
      // paint before we consider them done.
      !painted
    ) {
      LogInfo("AfterOnLoadScripts belatedly entering WaitForTestEnd");
      // Go into reftest-wait mode belatedly.
      WaitForTestEnd(contentRootElement, inPrintMode, [], uri);
    } else {
      CheckForProcessCrashExpectation(contentRootElement);
      RecordResult(uri);
    }
  }

  if (
    shouldWaitForReftestWaitRemoval(contentRootElement) ||
    spellCheckedElements.length
  ) {
    // Go into reftest-wait mode immediately after painting has been
    // unsuppressed, after the onload event has finished dispatching.
    gFailureReason =
      "timed out waiting for test to complete (trying to get into WaitForTestEnd)";
    LogInfo("OnDocumentLoad triggering WaitForTestEnd");
    setTimeout(function () {
      WaitForTestEnd(
        contentRootElement,
        inPrintMode,
        spellCheckedElements,
        uri
      );
    }, 0);
  } else {
    if (doPrintMode(contentRootElement)) {
      LogInfo("OnDocumentLoad setting up print mode");
      setupPrintMode(contentRootElement);
      inPrintMode = true;
    }

    // Since we can't use a bubbling-phase load listener from chrome,
    // this is a capturing phase listener.  So do setTimeout twice, the
    // first to get us after the onload has fired in the content, and
    // the second to get us after any setTimeout(foo, 0) in the content.
    gFailureReason =
      "timed out waiting for test to complete (waiting for onload scripts to complete)";
    LogInfo("OnDocumentLoad triggering AfterOnLoadScripts");
    setTimeout(function () {
      setTimeout(AfterOnLoadScripts, 0);
    }, 0);
  }
}

function CheckForProcessCrashExpectation(contentRootElement) {
  if (
    contentRootElement &&
    contentRootElement.hasAttribute("class") &&
    contentRootElement
      .getAttribute("class")
      .split(/\s+/)
      .includes("reftest-expect-process-crash")
  ) {
    SendExpectProcessCrash();
  }
}

async function RecordResult(forURL) {
  if (forURL != gCurrentURL) {
    LogInfo("RecordResult fired for previous document");
    return;
  }

  if (gCurrentURLRecordResults > 0) {
    LogInfo("RecordResult fired extra times");
    FinishTestItem();
    return;
  }
  gCurrentURLRecordResults++;

  LogInfo("RecordResult fired");

  var currentTestRunTime = Date.now() - gCurrentTestStartTime;

  clearTimeout(gFailureTimeout);
  gFailureReason = null;
  gFailureTimeout = null;
  gCurrentURL = null;
  gCurrentURLTargetType = undefined;

  if (gCurrentTestType == TYPE_PRINT) {
    printToPdf();
    return;
  }
  if (gCurrentTestType == TYPE_SCRIPT) {
    var error = "";
    var testwindow = content;

    if (testwindow.wrappedJSObject) {
      testwindow = testwindow.wrappedJSObject;
    }

    var testcases;
    if (
      !testwindow.getTestCases ||
      typeof testwindow.getTestCases != "function"
    ) {
      // Force an unexpected failure to alert the test author to fix the test.
      error = "test must provide a function getTestCases(). (SCRIPT)\n";
    } else if (!(testcases = testwindow.getTestCases())) {
      // Force an unexpected failure to alert the test author to fix the test.
      error =
        "test's getTestCases() must return an Array-like Object. (SCRIPT)\n";
    } else if (!testcases.length) {
      // This failure may be due to a JavaScript Engine bug causing
      // early termination of the test. If we do not allow silent
      // failure, the driver will report an error.
    }

    var results = [];
    if (!error) {
      // FIXME/bug 618176: temporary workaround
      for (var i = 0; i < testcases.length; ++i) {
        var test = testcases[i];
        results.push({
          passed: test.testPassed(),
          description: test.testDescription(),
        });
      }
      //results = testcases.map(function(test) {
      //        return { passed: test.testPassed(),
      //                 description: test.testDescription() };
    }

    SendScriptResults(currentTestRunTime, error, results);
    FinishTestItem();
    return;
  }

  // Setup async scroll offsets now in case SynchronizeForSnapshot is not
  // called (due to reftest-no-sync-layers being supplied, or in the single
  // process case).
  let changedAsyncScrollZoom = await setupAsyncScrollOffsets({
    allowFailure: true,
  });
  if (setupAsyncZoom({ allowFailure: true })) {
    changedAsyncScrollZoom = true;
  }
  if (changedAsyncScrollZoom && !gBrowserIsRemote) {
    sendAsyncMessage("reftest:UpdateWholeCanvasForInvalidation");
  }

  SendTestDone(currentTestRunTime);
  FinishTestItem();
}

function LoadFailed() {
  if (gTimeoutHook) {
    gTimeoutHook();
  }
  gFailureTimeout = null;
  SendFailedLoad(gFailureReason);
}

function FinishTestItem() {
  gHaveCanvasSnapshot = false;
}

function DoAssertionCheck() {
  gClearingForAssertionCheck = false;

  var numAsserts = 0;
  if (gDebug.isDebugBuild) {
    var newAssertionCount = gDebug.assertionCount;
    numAsserts = newAssertionCount - gAssertionCount;
    gAssertionCount = newAssertionCount;
  }
  SendAssertionCount(numAsserts);
}

function LoadURI(uri) {
  let loadURIOptions = {
    triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
  };
  webNavigation().loadURI(Services.io.newURI(uri), loadURIOptions);
}

function LogError(str) {
  if (gVerbose) {
    sendSyncMessage("reftest:Log", { type: "error", msg: str });
  } else {
    sendAsyncMessage("reftest:Log", { type: "error", msg: str });
  }
}

function LogWarning(str) {
  if (gVerbose) {
    sendSyncMessage("reftest:Log", { type: "warning", msg: str });
  } else {
    sendAsyncMessage("reftest:Log", { type: "warning", msg: str });
  }
}

function LogInfo(str) {
  if (gVerbose) {
    sendSyncMessage("reftest:Log", { type: "info", msg: str });
  } else {
    sendAsyncMessage("reftest:Log", { type: "info", msg: str });
  }
}

function IsSnapshottableTestType() {
  // Script, load-only, and PDF-print tests do not need any snapshotting.
  return !(
    gCurrentTestType == TYPE_SCRIPT ||
    gCurrentTestType == TYPE_LOAD ||
    gCurrentTestType == TYPE_PRINT
  );
}

const SYNC_DEFAULT = 0x0;
const SYNC_ALLOW_DISABLE = 0x1;
// Returns a promise that resolve when the snapshot is done.
function SynchronizeForSnapshot(flags) {
  if (!IsSnapshottableTestType()) {
    return Promise.resolve(undefined);
  }

  if (flags & SYNC_ALLOW_DISABLE) {
    var docElt = content.document.documentElement;
    if (
      docElt &&
      (docElt.hasAttribute("reftest-no-sync-layers") || shouldNotFlush(docElt))
    ) {
      LogInfo("Test file chose to skip SynchronizeForSnapshot");
      return Promise.resolve(undefined);
    }
  }

  let browsingContext = content.docShell.browsingContext;
  let promise = content.windowGlobalChild
    .getActor("ReftestFission")
    .sendQuery("UpdateLayerTree", { browsingContext });
  return promise.then(
    function (result) {
      for (let errorString of result.errorStrings) {
        LogError(errorString);
      }
      for (let infoString of result.infoStrings) {
        LogInfo(infoString);
      }

      // Setup async scroll offsets now, because any scrollable layers should
      // have had their AsyncPanZoomControllers created.
      return setupAsyncScrollOffsets({ allowFailure: false }).then(function () {
        setupAsyncZoom({ allowFailure: false });
      });
    },
    function (reason) {
      // We expect actors to go away causing sendQuery's to fail, so
      // just note it.
      LogInfo("UpdateLayerTree sendQuery to parent rejected: " + reason);

      // Setup async scroll offsets now, because any scrollable layers should
      // have had their AsyncPanZoomControllers created.
      return setupAsyncScrollOffsets({ allowFailure: false }).then(function () {
        setupAsyncZoom({ allowFailure: false });
      });
    }
  );
}

function RegisterMessageListeners() {
  addMessageListener("reftest:Clear"function () {
    RecvClear();
  });
  addMessageListener("reftest:LoadScriptTest"function (m) {
    RecvLoadScriptTest(m.json.uri, m.json.timeout);
  });
  addMessageListener("reftest:LoadPrintTest"function (m) {
    RecvLoadPrintTest(m.json.uri, m.json.timeout);
  });
  addMessageListener("reftest:LoadTest"function (m) {
    RecvLoadTest(m.json.type, m.json.uri, m.json.uriTargetType, m.json.timeout);
  });
  addMessageListener("reftest:ResetRenderingState"function () {
    RecvResetRenderingState();
  });
  addMessageListener("reftest:PrintDone"function (m) {
    RecvPrintDone(m.json.status, m.json.fileName);
  });
  addMessageListener("reftest:UpdateCanvasWithSnapshotDone"function (m) {
    RecvUpdateCanvasWithSnapshotDone(m.json.painted);
  });
}

function RecvClear() {
  gClearingForAssertionCheck = true;
  LoadURI(BLANK_URL_FOR_CLEARING);
}

function RecvLoadTest(type, uri, uriTargetType, timeout) {
  StartTestURI(type, uri, uriTargetType, timeout);
}

function RecvLoadScriptTest(uri, timeout) {
  StartTestURI(TYPE_SCRIPT, uri, URL_TARGET_TYPE_TEST, timeout);
}

function RecvLoadPrintTest(uri, timeout) {
  StartTestURI(TYPE_PRINT, uri, URL_TARGET_TYPE_TEST, timeout);
}

function RecvResetRenderingState() {
  resetZoomAndTextZoom();
  resetDisplayportAndViewport();
}

function RecvPrintDone(status, fileName) {
  const currentTestRunTime = Date.now() - gCurrentTestStartTime;
  SendPrintResult(currentTestRunTime, status, fileName);
  FinishTestItem();
}

function RecvUpdateCanvasWithSnapshotDone(painted) {
  gUpdateCanvasPromiseResolver(painted);
}

function SendAssertionCount(numAssertions) {
  sendAsyncMessage("reftest:AssertionCount", { count: numAssertions });
}

function SendContentReady() {
  let gfxInfo =
    NS_GFXINFO_CONTRACTID in Cc &&
    Cc[NS_GFXINFO_CONTRACTID].getService(Ci.nsIGfxInfo);

  let info = {};

  try {
    info.D2DEnabled = gfxInfo.D2DEnabled;
    info.DWriteEnabled = gfxInfo.DWriteEnabled;
    info.EmbeddedInFirefoxReality = gfxInfo.EmbeddedInFirefoxReality;
  } catch (e) {
    info.D2DEnabled = false;
    info.DWriteEnabled = false;
    info.EmbeddedInFirefoxReality = false;
  }

  info.AzureCanvasBackend = gfxInfo.AzureCanvasBackend;
  info.AzureContentBackend = gfxInfo.AzureContentBackend;

  return sendSyncMessage("reftest:ContentReady", { gfx: info })[0];
}

function SendException(what) {
  sendAsyncMessage("reftest:Exception", { what });
}

function SendFailedLoad(why) {
  sendAsyncMessage("reftest:FailedLoad", { why });
}

function SendFailedNoPaint() {
  sendAsyncMessage("reftest:FailedNoPaint");
}

function SendFailedNoDisplayList() {
  sendAsyncMessage("reftest:FailedNoDisplayList");
}

function SendFailedDisplayList() {
  sendAsyncMessage("reftest:FailedDisplayList");
}

function SendFailedOpaqueLayer(why) {
  sendAsyncMessage("reftest:FailedOpaqueLayer", { why });
}

function SendFailedAssignedLayer(why) {
  sendAsyncMessage("reftest:FailedAssignedLayer", { why });
}

// Returns a promise that resolves to a bool that indicates if a snapshot was taken.
async function SendInitCanvasWithSnapshot(forURL) {
  if (forURL != gCurrentURL) {
    LogInfo("SendInitCanvasWithSnapshot called for previous document");
    // Lie and say we painted because it doesn't matter, this is a test we
    // are already done with that is clearing out. Then AfterOnLoadScripts
    // should finish quicker if that is who is calling us.
    return Promise.resolve(true);
  }

  // If we're in the same process as the top-level XUL window, then
  // drawing that window will also update our layers, so no
  // synchronization is needed.
  //
  // NB: this is a test-harness optimization only, it must not
  // affect the validity of the tests.
  if (gBrowserIsRemote) {
    await SynchronizeForSnapshot(SYNC_DEFAULT);
    let promise = new Promise(resolve => {
      gUpdateCanvasPromiseResolver = resolve;
    });
    sendAsyncMessage("reftest:InitCanvasWithSnapshot");

    gHaveCanvasSnapshot = await promise;
    return gHaveCanvasSnapshot;
  }

  // For in-process browser, we have to make a synchronous request
  // here to make the above optimization valid, so that MozWaitPaint
  // events dispatched (synchronously) during painting are received
  // before we check the paint-wait counter.  For out-of-process
  // browser though, it doesn't wrt correctness whether this request
  // is sync or async.
  let promise = new Promise(resolve => {
    gUpdateCanvasPromiseResolver = resolve;
  });
  sendAsyncMessage("reftest:InitCanvasWithSnapshot");

  gHaveCanvasSnapshot = await promise;
  return Promise.resolve(gHaveCanvasSnapshot);
}

function SendScriptResults(runtimeMs, error, results) {
  sendAsyncMessage("reftest:ScriptResults", {
    runtimeMs,
    error,
    results,
  });
}

function SendStartPrint(isPrintSelection, printRange) {
  sendAsyncMessage("reftest:StartPrint", { isPrintSelection, printRange });
}

function SendPrintResult(runtimeMs, status, fileName) {
  sendAsyncMessage("reftest:PrintResult", {
    runtimeMs,
    status,
    fileName,
  });
}

function SendExpectProcessCrash() {
  sendAsyncMessage("reftest:ExpectProcessCrash");
}

function SendTestDone(runtimeMs) {
  sendAsyncMessage("reftest:TestDone", { runtimeMs });
}

function roundTo(x, fraction) {
  return Math.round(x / fraction) * fraction;
}

function elementDescription(element) {
  return (
    "<" +
    element.localName +
    [].slice
      .call(element.attributes)
      .map(attr => ` ${attr.nodeName}="${attr.value}"`)
      .join("") +
    ">"
  );
}

async function SendUpdateCanvasForEvent(forURL, rectList, contentRootElement) {
  if (forURL != gCurrentURL) {
    LogInfo("SendUpdateCanvasForEvent called for previous document");
    // This is a test we are already done with that is clearing out.
    // Don't do anything.
    return;
  }

  var scale = docShell.browsingContext.fullZoom;

  var rects = [];
  if (shouldSnapshotWholePage(contentRootElement)) {
    // See comments in SendInitCanvasWithSnapshot() re: the split
    // logic here.
    if (!gBrowserIsRemote) {
      sendSyncMessage("reftest:UpdateWholeCanvasForInvalidation");
    } else {
      await SynchronizeForSnapshot(SYNC_ALLOW_DISABLE);
      let promise = new Promise(resolve => {
        gUpdateCanvasPromiseResolver = resolve;
      });
      sendAsyncMessage("reftest:UpdateWholeCanvasForInvalidation");
      await promise;
    }
    return;
  }

  var message;

  if (!windowUtils().isMozAfterPaintPending) {
    // Webrender doesn't have invalidation, and animations on the compositor
    // don't invoke any MozAfterEvent which means we have no invalidated
    // rect so we just invalidate the whole screen once we don't have
    // anymore paints pending. This will force the snapshot.

    LogInfo("Sending update whole canvas for invalidation");
    message = "reftest:UpdateWholeCanvasForInvalidation";
  } else {
    LogInfo("SendUpdateCanvasForEvent with " + rectList.length + " rects");
    for (var i = 0; i < rectList.length; ++i) {
      var r = rectList[i];
      // Set left/top/right/bottom to "device pixel" boundaries
      var left = Math.floor(roundTo(r.left * scale, 0.001));
      var top = Math.floor(roundTo(r.top * scale, 0.001));
      var right = Math.ceil(roundTo(r.right * scale, 0.001));
      var bottom = Math.ceil(roundTo(r.bottom * scale, 0.001));
      LogInfo("Rect: " + left + " " + top + " " + right + " " + bottom);

      rects.push({ left, top, right, bottom });
    }

    message = "reftest:UpdateCanvasForInvalidation";
  }

  // See comments in SendInitCanvasWithSnapshot() re: the split
  // logic here.
  if (!gBrowserIsRemote) {
    sendSyncMessage(message, { rects });
  } else {
    await SynchronizeForSnapshot(SYNC_ALLOW_DISABLE);
    let promise = new Promise(resolve => {
      gUpdateCanvasPromiseResolver = resolve;
    });
    sendAsyncMessage(message, { rects });
    await promise;
  }
}

if (content.document.readyState == "complete") {
  // load event has already fired for content, get started
  OnInitialLoad();
else {
  addEventListener("load", OnInitialLoad, true);
}

Messung V0.5
C=87 H=92 G=89

¤ Dauer der Verarbeitung: 0.29 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.