Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/gfx/layers/apz/test/mochitest/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 43 kB image not shown  

SSL apz_test_utils.js   Sprache: JAVA

 
// Utilities for writing APZ tests using the framework added in bug 961289

// ----------------------------------------------------------------------
// Functions that convert the APZ test data into a more usable form.
// Every place we have a WebIDL sequence whose elements are dictionaries
// with two elements, a key, and a value, we convert this into a JS
// object with a property for each key/value pair. (This is the structure
// we really want, but we can't express in directly in WebIDL.)
// ----------------------------------------------------------------------

// getHitTestConfig() expects apz_test_native_event_utils.js to be loaded as well.
/* import-globals-from apz_test_native_event_utils.js */

function convertEntries(entries) {
  var result = {};
  for (var i = 0; i < entries.length; ++i) {
    result[entries[i].key] = entries[i].value;
  }
  return result;
}

function parsePoint(str) {
  var pieces = str.replace(/[()\s]+/g, "").split(",");
  SimpleTest.is(pieces.length, 2, "expected string of form (x,y)");
  for (var i = 0; i < 2; i++) {
    var eq = pieces[i].indexOf("=");
    if (eq >= 0) {
      pieces[i] = pieces[i].substring(eq + 1);
    }
  }
  return {
    x: parseInt(pieces[0]),
    y: parseInt(pieces[1]),
  };
}

// Given a VisualViewport object, return the visual viewport
// rect relative to the page.
function getVisualViewportRect(vv) {
  return {
    x: vv.pageLeft,
    y: vv.pageTop,
    width: vv.width,
    height: vv.height,
  };
}

// Return the offset of the visual viewport relative to the layout viewport.
function getRelativeViewportOffset(window) {
  const offsetX = {};
  const offsetY = {};
  const utils = SpecialPowers.getDOMWindowUtils(window);
  utils.getVisualViewportOffsetRelativeToLayoutViewport(offsetX, offsetY);
  return {
    x: offsetX.value,
    y: offsetY.value,
  };
}

function parseRect(str) {
  var pieces = str.replace(/[()\s]+/g, "").split(",");
  SimpleTest.is(pieces.length, 4, "expected string of form (x,y,w,h)");
  for (var i = 0; i < 4; i++) {
    var eq = pieces[i].indexOf("=");
    if (eq >= 0) {
      pieces[i] = pieces[i].substring(eq + 1);
    }
  }
  return {
    x: parseInt(pieces[0]),
    y: parseInt(pieces[1]),
    width: parseInt(pieces[2]),
    height: parseInt(pieces[3]),
  };
}

// These functions expect rects with fields named x/y/width/height, such as
// that returned by parseRect().
function rectContains(haystack, needle) {
  return (
    haystack.x <= needle.x &&
    haystack.y <= needle.y &&
    haystack.x + haystack.width >= needle.x + needle.width &&
    haystack.y + haystack.height >= needle.y + needle.height
  );
}
function rectToString(rect) {
  return (
    "(" + rect.x + "," + rect.y + "," + rect.width + "," + rect.height + ")"
  );
}
function assertRectContainment(
  haystackRect,
  haystackDesc,
  needleRect,
  needleDesc
) {
  SimpleTest.ok(
    rectContains(haystackRect, needleRect),
    haystackDesc +
      " " +
      rectToString(haystackRect) +
      " should contain " +
      needleDesc +
      " " +
      rectToString(needleRect)
  );
}

function getPropertyAsRect(scrollFrames, scrollId, prop) {
  SimpleTest.ok(
    scrollId in scrollFrames,
    "expected scroll frame data for scroll id " + scrollId
  );
  var scrollFrameData = scrollFrames[scrollId];
  SimpleTest.ok(
    "displayport" in scrollFrameData,
    "expected a " + prop + " for scroll id " + scrollId
  );
  var value = scrollFrameData[prop];
  return parseRect(value);
}

function convertScrollFrameData(scrollFrames) {
  var result = {};
  for (var i = 0; i < scrollFrames.length; ++i) {
    result[scrollFrames[i].scrollId] = convertEntries(scrollFrames[i].entries);
  }
  return result;
}

function convertBuckets(buckets) {
  var result = {};
  for (var i = 0; i < buckets.length; ++i) {
    result[buckets[i].sequenceNumber] = convertScrollFrameData(
      buckets[i].scrollFrames
    );
  }
  return result;
}

function convertTestData(testData) {
  var result = {};
  result.paints = convertBuckets(testData.paints);
  result.repaintRequests = convertBuckets(testData.repaintRequests);
  return result;
}

// Returns the last bucket that has at least one scrollframe. This
// is useful for skipping over buckets that are from empty transactions,
// because those don't contain any useful data.
function getLastNonemptyBucket(buckets) {
  for (var i = buckets.length - 1; i >= 0; --i) {
    if (buckets[i].scrollFrames.length) {
      return buckets[i];
    }
  }
  return null;
}

// Takes something like "matrix(1, 0, 0, 1, 234.024, 528.29023)"" and returns a number array
function parseTransform(transform) {
  return /matrix\((.*),(.*),(.*),(.*),(.*),(.*)\)/
    .exec(transform)
    .slice(1)
    .map(parseFloat);
}

function isTransformClose(a, b, name) {
  is(
    a.length,
    b.length,
    `expected transforms ${a} and ${b} to be the same length`
  );
  for (let i = 0; i < a.length; i++) {
    ok(Math.abs(a[i] - b[i]) < 0.01, name);
  }
}

// Given APZ test data for a single paint on the compositor side,
// reconstruct the APZC tree structure from the 'parentScrollId'
// entries that were logged. More specifically, the subset of the
// APZC tree structure corresponding to the layer subtree for the
// content process that triggered the paint, is reconstructed (as
// the APZ test data only contains information abot this subtree).
function buildApzcTree(paint) {
  // The APZC tree can potentially have multiple root nodes,
  // so we invent a node that is the parent of all roots.
  // This 'root' does not correspond to an APZC.
  var root = { scrollId: -1, children: [] };
  for (let scrollId in paint) {
    paint[scrollId].children = [];
    paint[scrollId].scrollId = scrollId;
  }
  for (let scrollId in paint) {
    var parentNode = null;
    if ("hasNoParentWithSameLayersId" in paint[scrollId]) {
      parentNode = root;
    } else if ("parentScrollId" in paint[scrollId]) {
      parentNode = paint[paint[scrollId].parentScrollId];
    }
    parentNode.children.push(paint[scrollId]);
  }
  return root;
}

// Given an APZC tree produced by buildApzcTree, return the RCD node in
// the tree, or null if there was none.
function findRcdNode(apzcTree) {
  // isRootContent will be undefined or "1"
  if (apzcTree.isRootContent) {
    return apzcTree;
  }
  for (var i = 0; i < apzcTree.children.length; i++) {
    var rcd = findRcdNode(apzcTree.children[i]);
    if (rcd != null) {
      return rcd;
    }
  }
  return null;
}

// Return whether an element whose id includes |elementId| has been layerized.
// Assumes |elementId| will be present in the content description for the
// element, and not in the content descriptions of other elements.
function isLayerized(elementId) {
  var contentTestData =
    SpecialPowers.getDOMWindowUtils(window).getContentAPZTestData();
  var nonEmptyBucket = getLastNonemptyBucket(contentTestData.paints);
  ok(nonEmptyBucket != null"expected at least one nonempty paint");
  var seqno = nonEmptyBucket.sequenceNumber;
  contentTestData = convertTestData(contentTestData);
  var paint = contentTestData.paints[seqno];
  for (var scrollId in paint) {
    if ("contentDescription" in paint[scrollId]) {
      if (paint[scrollId].contentDescription.includes(elementId)) {
        return true;
      }
    }
  }
  return false;
}

// Return a rect (or null) that holds the last known content-side displayport
// for a given element. (The element selection works the same way, and with
// the same assumptions as the isLayerized function above).
function getLastContentDisplayportFor(elementId, expectPainted = true) {
  var contentTestData =
    SpecialPowers.getDOMWindowUtils(window).getContentAPZTestData();
  if (contentTestData == undefined) {
    ok(!expectPainted, "expected to have apz test data (1)");
    return null;
  }
  var nonEmptyBucket = getLastNonemptyBucket(contentTestData.paints);
  if (nonEmptyBucket == null) {
    ok(!expectPainted, "expected to have apz test data (2)");
    return null;
  }
  var seqno = nonEmptyBucket.sequenceNumber;
  contentTestData = convertTestData(contentTestData);
  var paint = contentTestData.paints[seqno];
  for (var scrollId in paint) {
    if ("contentDescription" in paint[scrollId]) {
      if (paint[scrollId].contentDescription.includes(elementId)) {
        if ("displayport" in paint[scrollId]) {
          return parseRect(paint[scrollId].displayport);
        }
      }
    }
  }
  return null;
}

// Return the APZC tree (as produced by buildApzcTree) for the last
// non-empty paint received by the compositor.
function getLastApzcTree() {
  let data = SpecialPowers.getDOMWindowUtils(window).getCompositorAPZTestData();
  if (data == undefined) {
    ok(false"expected to have compositor apz test data");
    return null;
  }
  if (!data.paints.length) {
    ok(false"expected to have at least one compositor paint bucket");
    return null;
  }
  var seqno = data.paints[data.paints.length - 1].sequenceNumber;
  data = convertTestData(data);
  return buildApzcTree(data.paints[seqno]);
}

// Return a promise that is resolved on the next rAF callback
function promiseFrame(aWindow = window) {
  return new Promise(resolve => {
    aWindow.requestAnimationFrame(resolve);
  });
}

// Return a promise that is resolved on the next MozAfterPaint event
function promiseAfterPaint() {
  return new Promise(resolve => {
    window.addEventListener("MozAfterPaint", resolve, { once: true });
  });
}

// This waits until any pending events on the APZ controller thread are
// processed, and any resulting repaint requests are received by the main
// thread. Note that while the repaint requests do get processed by the
// APZ handler on the main thread, the repaints themselves may not have
// occurred by the the returned promise resolves. If you want to wait
// for those repaints, consider using promiseApzFlushedRepaints instead.
function promiseOnlyApzControllerFlushedWithoutSetTimeout(
  aWindow = window,
  aElement
) {
  return new Promise(function (resolve) {
    var fail = false;
    var repaintDone = function () {
      dump("PromiseApzRepaintsFlushed: APZ flush done\n");
      SpecialPowers.Services.obs.removeObserver(
        repaintDone,
        "apz-repaints-flushed"
      );
      resolve(!fail);
    };
    SpecialPowers.Services.obs.addObserver(repaintDone, "apz-repaints-flushed");
    if (SpecialPowers.getDOMWindowUtils(aWindow).flushApzRepaints(aElement)) {
      dump(
        "PromiseApzRepaintsFlushed: Flushed APZ repaints, waiting for callback...\n"
      );
    } else {
      dump(
        "PromiseApzRepaintsFlushed: Flushing APZ repaints was a no-op, triggering callback directly...\n"
      );
      fail = true;
      repaintDone();
    }
  });
}

// Another variant of the above promiseOnlyApzControllerFlushedWithoutSetTimeout
// but with a setTimeout(0) callback.
// |aElement| is an optional argument to do
// promiseOnlyApzControllerFlushedWithoutSetTimeout for the given |aElement|
// rather than |aWindow|. If you want to do "apz-repaints-flushed" in popup
// windows, you need to specify the element inside the popup window.
function promiseOnlyApzControllerFlushed(aWindow = window, aElement) {
  return new Promise(resolve => {
    promiseOnlyApzControllerFlushedWithoutSetTimeout(aWindow, aElement).then(
      result => {
        setTimeout(() => resolve(result), 0);
      }
    );
  });
}

// Flush repaints, APZ pending repaints, and any repaints resulting from that
// flush. This is particularly useful if the test needs to reach some sort of
// "idle" state in terms of repaints. Usually just waiting for all paints
// followed by flushApzRepaints is sufficient to flush all APZ state back to
// the main thread, but it can leave a paint scheduled which will get triggered
// at some later time. For tests that specifically test for painting at
// specific times, this method is the way to go. Even if in doubt, this is the
// preferred method as the extra step is "safe" and shouldn't interfere with
// most tests.
// If you want to do the flush in popup windows, you need to specify |aPopupElement|.
async function promiseApzFlushedRepaints(aPopupElement = null) {
  if (aPopupElement) {
    SimpleTest.ok(XULPopupElement.isInstance(aPopupElement));
  }
  await promiseAllPaintsDone();
  await promiseOnlyApzControllerFlushed(
    aPopupElement ? aPopupElement.ownerGlobal : window,
    aPopupElement
  );
  await promiseAllPaintsDone();
}

// This function takes a set of subtests to run one at a time in new top-level
// windows, and returns a Promise that is resolved once all the subtests are
// done running.
//
// The aSubtests array is an array of objects with the following keys:
//   file: required, the filename of the subtest.
//   prefs: optional, an array of arrays containing key-value prefs to set.
//   dp_suppression: optional, a boolean on whether or not to respect displayport
//                   suppression during the test.
//   onload: optional, a function that will be registered as a load event listener
//           for the child window that will hold the subtest. the function will be
//           passed exactly one argument, which will be the child window.
// An example of an array is:
//   aSubtests = [
//     { 'file': 'test_file_name.html' },
//     { 'file': 'test_file_2.html', 'prefs': [['pref.name', true], ['other.pref', 1000]], 'dp_suppression': false }
//     { 'file': 'file_3.html', 'onload': function(w) { w.subtestDone(); } }
//   ];
//
// Each subtest should call one of the subtestDone() or subtestFailed()
// functions when it is done, to indicate that the window should be torn
// down and the next test should run.
// These functions are injected into the subtest's window by this
// function prior to loading the subtest. For convenience, the |is| and |ok|
// functions provided by SimpleTest are also mapped into the subtest's window.
// For other things from the parent, the subtest can use window.opener.<whatever>
// to access objects.
function runSubtestsSeriallyInFreshWindows(aSubtests) {
  return new Promise(function (resolve, reject) {
    var testIndex = -1;
    var w = null;

    // If the "apz.subtest" pref has been set, only a single subtest whose name matches
    // the pref's value (if any) will be run.
    var onlyOneSubtest = SpecialPowers.getCharPref(
      "apz.subtest",
      /* default = */ ""
    );

    function advanceSubtestExecutionWithFailure(msg) {
      SimpleTest.ok(false, msg);
      advanceSubtestExecution();
    }

    async function advanceSubtestExecution() {
      var test = aSubtests[testIndex];
      if (w) {
        // Run any cleanup functions registered in the subtest
        // Guard against the subtest not loading apz_test_utils.js
        if (w.ApzCleanup) {
          w.ApzCleanup.execute();
        }
        if (typeof test.dp_suppression != "undefined") {
          // We modified the suppression when starting the test, so now undo that.
          SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression(
            !test.dp_suppression
          );
        }

        if (test.prefs) {
          // We pushed some prefs for this test, pop them, and re-invoke
          // advanceSubtestExecution() after that's been processed
          SpecialPowers.popPrefEnv(function () {
            w.close();
            w = null;
            advanceSubtestExecution();
          });
          return;
        }

        w.close();
      }

      testIndex++;
      if (testIndex >= aSubtests.length) {
        resolve();
        return;
      }

      await SimpleTest.promiseFocus(window);

      test = aSubtests[testIndex];

      let recognizedProps = ["file""prefs""dp_suppression""onload"];
      for (let prop in test) {
        if (!recognizedProps.includes(prop)) {
          SimpleTest.ok(
            false,
            "Subtest " + test.file + " has unrecognized property '" + prop + "'"
          );
          setTimeout(function () {
            advanceSubtestExecution();
          }, 0);
          return;
        }
      }

      if (onlyOneSubtest && onlyOneSubtest != test.file) {
        SimpleTest.ok(
          true,
          "Skipping " +
            test.file +
            " because only " +
            onlyOneSubtest +
            " is being run"
        );
        setTimeout(function () {
          advanceSubtestExecution();
        }, 0);
        return;
      }

      SimpleTest.ok(true"Starting subtest " + test.file);

      if (typeof test.dp_suppression != "undefined") {
        // Normally during a test, the displayport will get suppressed during page
        // load, and unsuppressed at a non-deterministic time during the test. The
        // unsuppression can trigger a repaint which interferes with the test, so
        // to avoid that we can force the displayport to be unsuppressed for the
        // entire test which is more deterministic.
        SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression(
          test.dp_suppression
        );
      }

      function spawnTest(aFile) {
        w = window.open("""_blank");
        w.subtestDone = advanceSubtestExecution;
        w.subtestFailed = advanceSubtestExecutionWithFailure;
        w.isApzSubtest = true;
        w.SimpleTest = SimpleTest;
        w.dump = function (msg) {
          return dump(aFile + " | " + msg);
        };
        w.info = function (msg) {
          return info(aFile + " | " + msg);
        };
        w.is = function (a, b, msg) {
          return is(a, b, aFile + " | " + msg);
        };
        w.isnot = function (a, b, msg) {
          return isnot(a, b, aFile + " | " + msg);
        };
        w.isfuzzy = function (a, b, eps, msg) {
          return isfuzzy(a, b, eps, aFile + " | " + msg);
        };
        w.ok = function (cond, msg) {
          arguments[1] = aFile + " | " + msg;
          // Forward all arguments to SimpleTest.ok where we will check that ok() was
          // called with at most 2 arguments.
          return SimpleTest.ok.apply(SimpleTest, arguments);
        };
        w.todo_is = function (a, b, msg) {
          return todo_is(a, b, aFile + " | " + msg);
        };
        w.todo = function (cond, msg) {
          return todo(cond, aFile + " | " + msg);
        };
        if (test.onload) {
          w.addEventListener(
            "load",
            function () {
              test.onload(w);
            },
            { once: true }
          );
        }
        var subtestUrl =
          location.href.substring(0, location.href.lastIndexOf("/") + 1) +
          aFile;
        function urlResolves(url) {
          var request = new XMLHttpRequest();
          request.open("GET", url, false);
          request.send();
          return request.status !== 404;
        }
        if (!urlResolves(subtestUrl)) {
          SimpleTest.ok(
            false,
            "Subtest URL " +
              subtestUrl +
              " does not resolve. " +
              "Be sure it's present in the support-files section of mochitest.ini."
          );
          reject();
          return undefined;
        }
        w.location = subtestUrl;
        return w;
      }

      if (test.prefs) {
        // Got some prefs for this subtest, push them
        await SpecialPowers.pushPrefEnv({ set: test.prefs });
      }
      w = spawnTest(test.file);
    }

    advanceSubtestExecution();
  }).catch(function (e) {
    SimpleTest.ok(false"Error occurred while running subtests: " + e);
  });
}

function pushPrefs(prefs) {
  return SpecialPowers.pushPrefEnv({ set: prefs });
}

async function waitUntilApzStable() {
  await SimpleTest.promiseFocus(window);
  dump("WaitUntilApzStable: done promiseFocus\n");
  await promiseAllPaintsDone();
  dump("WaitUntilApzStable: done promiseAllPaintsDone\n");
  await promiseOnlyApzControllerFlushed();
  dump("WaitUntilApzStable: all done\n");
}

// This function returns a promise that is resolved after at least one paint
// has been sent and processed by the compositor. This function can force
// such a paint to happen if none are pending. This is useful to run after
// the waitUntilApzStable() but before reading the compositor-side APZ test
// data, because the test data for the content layers id only gets populated
// on content layer tree updates *after* the root layer tree has a RefLayer
// pointing to the contnet layer tree. waitUntilApzStable itself guarantees
// that the root layer tree is pointing to the content layer tree, but does
// not guarantee the subsequent paint; this function does that job.
async function forceLayerTreeToCompositor() {
  // Modify a style property to force a layout flush
  document.body.style.boxSizing = "border-box";
  var utils = SpecialPowers.getDOMWindowUtils(window);
  if (!utils.isMozAfterPaintPending) {
    dump("Forcing a paint since none was pending already...\n");
    var testMode = utils.isTestControllingRefreshes;
    utils.advanceTimeAndRefresh(0);
    if (!testMode) {
      utils.restoreNormalRefresh();
    }
  }
  await promiseAllPaintsDone(nulltrue);
  await promiseOnlyApzControllerFlushed();
}

function isApzEnabled() {
  var enabled = SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled;
  if (!enabled) {
    // All tests are required to have at least one assertion. Since APZ is
    // disabled, and the main test is presumably not going to run, we stick in
    // a dummy assertion here to keep the test passing.
    SimpleTest.ok(true"APZ is not enabled; this test will be skipped");
  }
  return enabled;
}

function isKeyApzEnabled() {
  return isApzEnabled() && SpecialPowers.getBoolPref("apz.keyboard.enabled");
}

// Take a snapshot of the given rect, *including compositor transforms* (i.e.
// includes async scroll transforms applied by APZ). If you don't need the
// compositor transforms, you can probably get away with using
// SpecialPowers.snapshotWindowWithOptions or one of the friendlier wrappers.
// The rect provided is expected to be relative to the screen, for example as
// returned by rectRelativeToScreen in apz_test_native_event_utils.js.
// Example usage:
//   var snapshot = getSnapshot(rectRelativeToScreen(myDiv));
// which will take a snapshot of the 'myDiv' element. Note that if part of the
// element is obscured by other things on top, the snapshot will include those
// things. If it is clipped by a scroll container, the snapshot will include
// that area anyway, so you will probably get parts of the scroll container in
// the snapshot. If the rect extends outside the browser window then the
// results are undefined.
// The snapshot is returned in the form of a data URL.
function getSnapshot(rect) {
  function parentProcessSnapshot() {
    /* eslint-env mozilla/chrome-script */
    addMessageListener("snapshot"function (parentRect) {
      var topWin = Services.wm.getMostRecentWindow("navigator:browser");
      if (!topWin) {
        topWin = Services.wm.getMostRecentWindow("navigator:geckoview");
      }

      // reposition the rect relative to the top-level browser window
      parentRect = JSON.parse(parentRect);
      parentRect.x -= topWin.mozInnerScreenX;
      parentRect.y -= topWin.mozInnerScreenY;

      // take the snapshot
      var canvas = topWin.document.createElementNS(
        "http://www.w3.org/1999/xhtml",
        "canvas"
      );
      canvas.width = parentRect.width;
      canvas.height = parentRect.height;
      var ctx = canvas.getContext("2d");
      ctx.drawWindow(
        topWin,
        parentRect.x,
        parentRect.y,
        parentRect.width,
        parentRect.height,
        "rgb(255,255,255)",
        ctx.DRAWWINDOW_DRAW_VIEW |
          ctx.DRAWWINDOW_USE_WIDGET_LAYERS |
          ctx.DRAWWINDOW_DRAW_CARET
      );
      return canvas.toDataURL();
    });
  }

  if (typeof getSnapshot.chromeHelper == "undefined") {
    // This is the first time getSnapshot is being called; do initialization
    getSnapshot.chromeHelper = SpecialPowers.loadChromeScript(
      parentProcessSnapshot
    );
    ApzCleanup.register(function () {
      getSnapshot.chromeHelper.destroy();
    });
  }

  return getSnapshot.chromeHelper.sendQuery("snapshot", JSON.stringify(rect));
}

// Takes the document's query string and parses it, assuming the query string
// is composed of key-value pairs where the value is in JSON format. The object
// returned contains the various values indexed by their respective keys. In
// case of duplicate keys, the last value be used.
// Examples:
//   ?key="value"&key2=false&key3=500
//     produces { "key": "value", "key2": false, "key3": 500 }
//   ?key={"x":0,"y":50}&key2=[1,2,true]
//     produces { "key": { "x": 0, "y": 0 }, "key2": [1, 2, true] }
function getQueryArgs() {
  var args = {};
  if (location.search.length) {
    var params = location.search.substr(1).split("&");
    for (var p of params) {
      var [k, v] = p.split("=");
      args[k] = JSON.parse(v);
    }
  }
  return args;
}

// An async function that inserts a script element with the given URI into
// the head of the document of the given window. This function returns when
// the load or error event fires on the script element, indicating completion.
async function injectScript(aScript, aWindow = window) {
  var e = aWindow.document.createElement("script");
  e.type = "text/javascript";
  let loadPromise = new Promise((resolve, reject) => {
    e.onload = function () {
      resolve();
    };
    e.onerror = function () {
      dump("Script [" + aScript + "] errored out\n");
      reject();
    };
  });
  e.src = aScript;
  aWindow.document.getElementsByTagName("head")[0].appendChild(e);
  await loadPromise;
}

// Compute some configuration information used for hit testing.
// The computed information is cached to avoid recomputing it
// each time this function is called.
// The computed information is an object with three fields:
//   utils: the nsIDOMWindowUtils instance for this window
//   isWindow: true if the platform is Windows
//   activateAllScrollFrames: true if prefs indicate all scroll frames are
//                            activated with at least a minimal display port
function getHitTestConfig() {
  if (!("hitTestConfig" in window)) {
    var utils = SpecialPowers.getDOMWindowUtils(window);
    var isWindows = getPlatform() == "windows";
    let activateAllScrollFrames =
      SpecialPowers.getBoolPref("apz.wr.activate_all_scroll_frames") ||
      (SpecialPowers.getBoolPref(
        "apz.wr.activate_all_scroll_frames_when_fission"
      ) &&
        SpecialPowers.Services.appinfo.fissionAutostart);

    window.hitTestConfig = {
      utils,
      isWindows,
      activateAllScrollFrames,
    };
  }
  return window.hitTestConfig;
}

// Compute the coordinates of the center of the given element. The argument
// can either be a string (the id of the element desired) or the element
// itself.
function centerOf(element) {
  if (typeof element === "string") {
    element = document.getElementById(element);
  }
  var bounds = element.getBoundingClientRect();
  return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
}

// Peform a compositor hit test at the given point and return the result.
// |point| is expected to be in CSS coordinates relative to the layout
// viewport, since this is what sendMouseEvent() expects. (Note that this
// is different from sendNativeMouseEvent() which expects screen coordinates
// relative to the screen.)
// The returned object has two fields:
//   hitInfo: a combination of APZHitResultFlags
//   scrollId: the view-id of the scroll frame that was hit
function hitTest(point) {
  var utils = getHitTestConfig().utils;
  dump("Hit-testing point (" + point.x + ", " + point.y + ")\n");
  utils.sendMouseEvent(
    "MozMouseHittest",
    point.x,
    point.y,
    0,
    0,
    0,
    true,
    0,
    0,
    true,
    true
  );
  var data = utils.getCompositorAPZTestData();
  ok(
    data.hitResults.length >= 1,
    "Expected at least one hit result in the APZTestData"
  );
  var result = data.hitResults[data.hitResults.length - 1];
  return {
    hitInfo: result.hitResult,
    scrollId: result.scrollId,
    layersId: result.layersId,
  };
}

// Returns a canonical stringification of the hitInfo bitfield.
function hitInfoToString(hitInfo) {
  var strs = [];
  for (var flag in APZHitResultFlags) {
    if ((hitInfo & APZHitResultFlags[flag]) != 0) {
      strs.push(flag);
    }
  }
  if (!strs.length) {
    return "INVISIBLE";
  }
  strs.sort(function (a, b) {
    return APZHitResultFlags[a] - APZHitResultFlags[b];
  });
  return strs.join(" | ");
}

// Takes an object returned by hitTest, along with the expected values, and
// asserts that they match. Notably, it uses hitInfoToString to provide a
// more useful message for the case that the hit info doesn't match
function checkHitResult(
  hitResult,
  expectedHitInfo,
  expectedScrollId,
  expectedLayersId,
  desc
) {
  is(
    hitInfoToString(hitResult.hitInfo),
    hitInfoToString(expectedHitInfo),
    desc + " hit info"
  );
  is(hitResult.scrollId, expectedScrollId, desc + " scrollid");
  is(hitResult.layersId, expectedLayersId, desc + " layersid");
}

// Symbolic constants used by hitTestScrollbar().
var ScrollbarTrackLocation = {
  START: 1,
  END: 2,
};
var LayerState = {
  ACTIVE: 1,
  INACTIVE: 2,
};

// Perform a hit test on the scrollbar(s) of a scroll frame.
// This function takes a single argument which is expected to be
// an object with the following fields:
//   element: The scroll frame to perform the hit test on.
//   directions: The direction(s) of scrollbars to test.
//     If directions.vertical is true, the vertical scrollbar will be tested.
//     If directions.horizontal is true, the horizontal scrollbar will be tested.
//     Both may be true in a single call (in which case two tests are performed).
//   expectedScrollId: The scroll id that is expected to be hit, if activateAllScrollFrames is false.
//   expectedLayersId: The layers id that is expected to be hit.
//   trackLocation: One of ScrollbarTrackLocation.{START, END}.
//     Determines which end of the scrollbar track is targeted.
//   expectThumb: Whether the scrollbar thumb is expected to be present
//     at the targeted end of the scrollbar track.
//   layerState: Whether the scroll frame is active or inactive.
// The function performs the hit tests and asserts that the returned
// hit test information is consistent with the passed parameters.
// There is no return value.
// Tests that use this function must set the pref
// "layout.scrollbars.always-layerize-track".
function hitTestScrollbar(params) {
  var config = getHitTestConfig();

  var elem = params.element;

  var boundingClientRect = elem.getBoundingClientRect();

  var verticalScrollbarWidth = boundingClientRect.width - elem.clientWidth;
  var horizontalScrollbarHeight = boundingClientRect.height - elem.clientHeight;

  // On windows, the scrollbar tracks have buttons on the end. When computing
  // coordinates for hit-testing we need to account for this. We assume the
  // buttons are square, and so can use the scrollbar width/height to estimate
  // the size of the buttons
  var scrollbarArrowButtonHeight = config.isWindows
    ? verticalScrollbarWidth
    : 0;
  var scrollbarArrowButtonWidth = config.isWindows
    ? horizontalScrollbarHeight
    : 0;

  // Compute the expected hit result flags.
  // The direction flag (APZHitResultFlags.SCROLLBAR_VERTICAL) is added in
  // later, for the vertical test only.
  // The APZHitResultFlags.SCROLLBAR flag will be present regardless of whether
  // the layer is active or inactive because we force layerization of scrollbar
  // tracks. Unfortunately not forcing the layerization results in different
  // behaviour on different platforms which makes testing harder.
  var expectedHitInfo = APZHitResultFlags.VISIBLE | APZHitResultFlags.SCROLLBAR;
  if (params.expectThumb) {
    // The thumb has listeners which are APZ-aware.
    expectedHitInfo |= APZHitResultFlags.APZ_AWARE_LISTENERS;
    var expectActive =
      config.activateAllScrollFrames || params.layerState == LayerState.ACTIVE;
    if (!expectActive) {
      expectedHitInfo |= APZHitResultFlags.INACTIVE_SCROLLFRAME;
    }
    // We do not generate the layers for thumbs on inactive scrollframes.
    if (expectActive) {
      expectedHitInfo |= APZHitResultFlags.SCROLLBAR_THUMB;
    }
  }

  var expectedScrollId = params.expectedScrollId;
  if (config.activateAllScrollFrames) {
    expectedScrollId = config.utils.getViewId(params.element);
    if (params.layerState == LayerState.ACTIVE) {
      is(
        expectedScrollId,
        params.expectedScrollId,
        "Expected scrollId for active scrollframe should match"
      );
    }
  }

  var scrollframeMsg =
    params.layerState == LayerState.ACTIVE
      ? "active scrollframe"
      : "inactive scrollframe";

  // Hit-test the targeted areas, assuming we don't have overlay scrollbars
  // with zero dimensions.
  if (params.directions.vertical && verticalScrollbarWidth > 0) {
    var verticalScrollbarPoint = {
      x: boundingClientRect.right - verticalScrollbarWidth / 2,
      y:
        params.trackLocation == ScrollbarTrackLocation.START
          ? boundingClientRect.y + scrollbarArrowButtonHeight + 5
          : boundingClientRect.bottom -
            horizontalScrollbarHeight -
            scrollbarArrowButtonHeight -
            5,
    };
    checkHitResult(
      hitTest(verticalScrollbarPoint),
      expectedHitInfo | APZHitResultFlags.SCROLLBAR_VERTICAL,
      expectedScrollId,
      params.expectedLayersId,
      scrollframeMsg + " - vertical scrollbar"
    );
  }

  if (params.directions.horizontal && horizontalScrollbarHeight > 0) {
    var horizontalScrollbarPoint = {
      x:
        params.trackLocation == ScrollbarTrackLocation.START
          ? boundingClientRect.x + scrollbarArrowButtonWidth + 5
          : boundingClientRect.right -
            verticalScrollbarWidth -
            scrollbarArrowButtonWidth -
            5,
      y: boundingClientRect.bottom - horizontalScrollbarHeight / 2,
    };
    checkHitResult(
      hitTest(horizontalScrollbarPoint),
      expectedHitInfo,
      expectedScrollId,
      params.expectedLayersId,
      scrollframeMsg + " - horizontal scrollbar"
    );
  }
}

// Return a list of prefs for the given test identifier.
function getPrefs(ident) {
  switch (ident) {
    case "TOUCH_EVENTS:PAN":
      return [
        // Dropping the touch slop to 0 makes the tests easier to write because
        // we can just do a one-pixel drag to get over the pan threshold rather
        // than having to hard-code some larger value.
        ["apz.touch_start_tolerance""0.0"],
        // The touchstart from the drag can turn into a long-tap if the touch-move
        // events get held up. Try to prevent that by making long-taps require
        // a 10 second hold. Note that we also cannot enable chaos mode on this
        // test for this reason, since chaos mode can cause the long-press timer
        // to fire sooner than the pref dictates.
        ["ui.click_hold_context_menus.delay", 10000],
        // The subtests in this test do touch-drags to pan the page, but we don't
        // want those pans to turn into fling animations, so we increase the
        // fling min velocity requirement absurdly high.
        ["apz.fling_min_velocity_threshold""10000"],
        // The helper_div_pan's div gets a displayport on scroll, but if the
        // test takes too long the displayport can expire before the new scroll
        // position is synced back to the main thread. So we disable displayport
        // expiry for these tests.
        ["apz.displayport_expiry_ms", 0],
        // We need to disable touch resampling during these tests because we
        // rely on touch move events being processed without delay. Touch
        // resampling only processes them once vsync fires.
        ["android.touch_resampling.enabled"false],
      ];
    case "TOUCH_ACTION":
      return [
        ...getPrefs("TOUCH_EVENTS:PAN"),
        ["apz.test.fails_with_native_injection", getPlatform() == "windows"],
      ];
    default:
      return [];
  }
}

var ApzCleanup = {
  _cleanups: [],

  register(func) {
    if (!this._cleanups.length) {
      if (!window.isApzSubtest) {
        SimpleTest.registerCleanupFunction(this.execute.bind(this));
      } // else ApzCleanup.execute is called from runSubtestsSeriallyInFreshWindows
    }
    this._cleanups.push(func);
  },

  execute() {
    while (this._cleanups.length) {
      var func = this._cleanups.pop();
      try {
        func();
      } catch (ex) {
        SimpleTest.ok(
          false,
          "Subtest cleanup function [" +
            func.toString() +
            "] threw exception [" +
            ex +
            "] on page [" +
            location.href +
            "]"
        );
      }
    }
  },
};

/**
 * Returns a promise that will resolve if `eventTarget` receives an event of the
 * given type that passes the given filter. Only the first matching message is
 * used. The filter must be a function (or null); it is called with the event
 * object and the call must return true to resolve the promise.
 */

function promiseOneEvent(eventTarget, eventType, filter) {
  return new Promise((resolve, reject) => {
    eventTarget.addEventListener(eventType, function listener(e) {
      let success = false;
      if (filter == null) {
        success = true;
      } else if (typeof filter == "function") {
        try {
          success = filter(e);
        } catch (ex) {
          dump(
            `ERROR: Filter passed to promiseOneEvent threw exception: ${ex}\n`
          );
          reject();
          return;
        }
      } else {
        dump(
          "ERROR: Filter passed to promiseOneEvent was neither null nor a function\n"
        );
        reject();
        return;
      }
      if (success) {
        eventTarget.removeEventListener(eventType, listener);
        resolve(e);
      }
    });
  });
}

function visualViewportAsZoomedRect() {
  let vv = window.visualViewport;
  return {
    x: vv.pageLeft,
    y: vv.pageTop,
    w: vv.width,
    h: vv.height,
    z: vv.scale,
  };
}

// Pulls the latest compositor APZ test data and checks to see if the
// scroller with id `scrollerId` was checkerboarding. It also ensures that
// a scroller with id `scrollerId` was actually found in the test data.
// This function requires that "apz.test.logging_enabled" be set to true,
// in order for the test data to be logged.
function assertNotCheckerboarded(utils, scrollerId, msgPrefix) {
  utils.advanceTimeAndRefresh(0);
  var data = utils.getCompositorAPZTestData();
  //dump(JSON.stringify(data, null, 4));
  var found = false;
  for (apzcData of data.additionalData) {
    if (apzcData.key == scrollerId) {
      var checkerboarding = apzcData.value
        .split(",")
        .includes("checkerboarding");
      ok(!checkerboarding, `${msgPrefix}: scroller is not checkerboarding`);
      found = true;
    }
  }
  ok(found, `${msgPrefix}: Found the scroller in the APZ data`);
  utils.restoreNormalRefresh();
}

async function waitToClearOutAnyPotentialScrolls(aWindow) {
  await promiseFrame(aWindow);
  await promiseFrame(aWindow);
  await promiseOnlyApzControllerFlushed(aWindow);
  await promiseFrame(aWindow);
  await promiseFrame(aWindow);
}

function waitForScrollEvent(target) {
  return new Promise(resolve => {
    target.addEventListener("scroll", resolve, { once: true });
  });
}

// This is another variant of promiseApzFlushedRepaints.
// We need this function because, unfortunately, there is no easy way to use
// paint_listeners.js' functions and apz_test_utils.js' functions in popup
// contents opened by extensions either as scripts in the popup contents or
// scripts inside SpecialPowers.spawn because we can't use privileged functions
// in the popup contents' script, we can't use functions basically as it as in
// the sandboxed context either.
async function promiseApzFlushedRepaintsInPopup(popup) {
  // Flush APZ repaints and waits for MozAfterPaint.
  await SpecialPowers.spawn(popup, [], async () => {
    const utils = SpecialPowers.getDOMWindowUtils(content.window);

    async function promiseAllPaintsDone() {
      return new Promise(resolve => {
        function waitForPaints() {
          if (utils.isMozAfterPaintPending) {
            dump("Waits for a MozAfterPaint event\n");
            content.window.addEventListener(
              "MozAfterPaint",
              () => {
                dump("Got a MozAfterPaint event\n");
                waitForPaints();
              },
              { once: true }
            );
          } else {
            dump("No more pending MozAfterPaint\n");
            content.window.setTimeout(resolve, 0);
          }
        }
        waitForPaints();
      });
    }
    await promiseAllPaintsDone();

    await new Promise(resolve => {
      var repaintDone = function () {
        dump("APZ flush done\n");
        SpecialPowers.Services.obs.removeObserver(
          repaintDone,
          "apz-repaints-flushed"
        );
        content.window.setTimeout(resolve, 0);
      };
      SpecialPowers.Services.obs.addObserver(
        repaintDone,
        "apz-repaints-flushed"
      );
      if (utils.flushApzRepaints()) {
        dump("Flushed APZ repaints, waiting for callback...\n");
      } else {
        dump(
          "Flushing APZ repaints was a no-op, triggering callback directly...\n"
        );
        repaintDone();
      }
    });

    await promiseAllPaintsDone();
  });
}

// A utility function to make sure there's no scroll animation on the given
// |aElement|.
async function cancelScrollAnimation(aElement, aWindow = window) {
  // In fact there's no good way to directly cancel the active animation on the
  // element, so we destroy the corresponding scrollable frame then reconstruct
  // a new scrollable frame so that it clobbers the animation.
  const originalStyle = aElement.style.display;
  aElement.style.display = "none";
  await aWindow.promiseApzFlushedRepaints();
  aElement.style.display = originalStyle;
  await aWindow.promiseApzFlushedRepaints();
}

function collectSampledScrollOffsets(aElement, aPopupElement = null) {
  let data =
    SpecialPowers.DOMWindowUtils.getCompositorAPZTestData(aPopupElement);
  let sampledResults = data.sampledResults;

  const layersId = SpecialPowers.DOMWindowUtils.getLayersId(aPopupElement);
  const scrollId = SpecialPowers.DOMWindowUtils.getViewId(aElement);

  return sampledResults.filter(
    result =>
      SpecialPowers.wrap(result).layersId == layersId &&
      SpecialPowers.wrap(result).scrollId == scrollId
  );
}

function cloneVisualViewport() {
  return {
    offsetLeft: visualViewport.offsetLeft,
    offsetTop: visualViewport.offsetTop,
    pageLeft: visualViewport.pageLeft,
    pageTop: visualViewport.pageTop,
    width: visualViewport.width,
    height: visualViewport.height,
    scale: visualViewport.scale,
  };
}

function compareVisualViewport(
  aVisualViewportValue1,
  aVisualViewportValue2,
  aMessage
) {
  for (let p in aVisualViewportValue1) {
    // Due to the method difference of the calculation for double-tap-zoom in
    // OOP iframes, we allow 1.0 difference in each visualViewport value.
    // NOTE: Because of our layer pixel snapping (bug 1774315 and bug 1852884)
    // the visual viewport metrics can have one more pixel difference so we
    // allow it here.
    const tolerance = 1.0 + 1.0;
    isfuzzy(
      aVisualViewportValue1[p],
      aVisualViewportValue2[p],
      aVisualViewportValue1.scale > 1.0
        ? tolerance
        : tolerance / aVisualViewportValue1.scale,
      `${p} should be same on ${aMessage}`
    );
  }
}

// Loads a URL in an iframe and waits until APZ is stable
async function setupIframe(aIFrame, aURL, aIsOffScreen = false) {
  const iframeLoadPromise = promiseOneEvent(aIFrame, "load"null);
  aIFrame.src = aURL;
  await iframeLoadPromise;

  if (!aIsOffScreen) {
    await SpecialPowers.spawn(aIFrame, [], async () => {
      await content.wrappedJSObject.waitUntilApzStable();
    });
  }
}

// Loads a URL in an iframe and replaces its origin to
// create an out-of-process iframe
async function setupCrossOriginIFrame(aIFrame, aUrl, aIsOffScreen = false) {
  let iframeURL = SimpleTest.getTestFileURL(aUrl);
  iframeURL = iframeURL.replace(window.location.origin, "https://example.com");
  await setupIframe(aIFrame, iframeURL, aIsOffScreen);
  if (!aIsOffScreen) {
    await SpecialPowers.spawn(aIFrame, [], async () => {
      await SpecialPowers.contentTransformsReceived(content);
    });
  }
}

Messung V0.5
C=80 H=92 G=86

¤ Dauer der Verarbeitung: 0.44 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.