tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

cloneElementVisually_helpers.js (6842B)


      1 const TEST_VIDEO_1 =
      2  "http://mochi.test:8888/tests/dom/media/test/bipbop_225w_175kbps.mp4";
      3 const TEST_VIDEO_2 =
      4  "http://mochi.test:8888/tests/dom/media/test/pixel_aspect_ratio.mp4";
      5 const LONG_VIDEO = "http://mochi.test:8888/tests/dom/media/test/gizmo.mp4";
      6 
      7 /**
      8 * Ensure that the original <video> is prepped and ready to play
      9 * before starting any other tests.
     10 */
     11 async function setup() {
     12  await SpecialPowers.pushPrefEnv({
     13    set: [
     14      ["media.test.video-suspend", true],
     15      ["media.suspend-background-video.enabled", true],
     16      ["media.suspend-background-video.delay-ms", 500],
     17      ["media.dormant-on-pause-timeout-ms", 0],
     18      ["media.cloneElementVisually.testing", true],
     19    ],
     20  });
     21 
     22  let originalVideo = document.getElementById("original");
     23  await setVideoSrc(originalVideo, TEST_VIDEO_1);
     24 }
     25 
     26 /**
     27 * Given a canvas, as well as a width and height of something to be
     28 * blitted onto that canvas, makes any necessary adjustments to the
     29 * canvas to work with the current display resolution.
     30 *
     31 * @param canvas (<canvas> element)
     32 *        The canvas to be adjusted.
     33 * @param width (int)
     34 *        The width of the image to be blitted.
     35 * @param height (int)
     36 *        The height of the image to be blitted.
     37 * @return CanvasRenderingContext2D (SpecialPowers wrapper)
     38 */
     39 function getWrappedScaledCanvasContext(canvas, width, height) {
     40  let devRatio = window.devicePixelRatio || 1;
     41  let ctx = SpecialPowers.wrap(canvas.getContext("2d"));
     42  let backingRatio = ctx.webkitBackingStorePixelRatio || 1;
     43 
     44  let ratio = 1;
     45  ratio = devRatio / backingRatio;
     46  canvas.width = ratio * width;
     47  canvas.height = ratio * height;
     48  canvas.style.width = width + "px";
     49  canvas.style.height = height + "px";
     50  ctx.scale(ratio, ratio);
     51 
     52  return ctx;
     53 }
     54 
     55 /**
     56 * Given a <video> element in the DOM, figures out its location, and captures
     57 * a snapshot of what it's currently presenting.
     58 *
     59 * @param video (<video> element)
     60 * @return ImageData (SpecialPowers wrapper)
     61 */
     62 function captureFrameImageData(video) {
     63  // Flush layout first, just in case the decoder has recently
     64  // updated the dimensions of the video.
     65  let rect = video.getBoundingClientRect();
     66 
     67  let width = video.videoWidth;
     68  let height = video.videoHeight;
     69 
     70  let canvas = document.createElement("canvas");
     71  let ctx = getWrappedScaledCanvasContext(canvas, width, height);
     72  ctx.drawWindow(window, rect.left, rect.top, width, height, "rgb(0,0,0)");
     73 
     74  return ctx.getImageData(0, 0, width, height);
     75 }
     76 
     77 /**
     78 * Given two <video> elements, captures snapshots of what they're currently
     79 * displaying, and asserts that they're identical.
     80 *
     81 * @param video1 (<video> element)
     82 *        A video element to compare against.
     83 * @param video2 (<video> element)
     84 *        A video to compare with video1.
     85 * @returns {Promise<boolean>}
     86 *   Resolves as true if the two videos match.
     87 */
     88 async function assertVideosMatch(video1, video2) {
     89  let video1Frame = captureFrameImageData(video1);
     90  let video2Frame = captureFrameImageData(video2);
     91 
     92  let left = document.getElementById("left");
     93  let leftCtx = getWrappedScaledCanvasContext(
     94    left,
     95    video1Frame.width,
     96    video1Frame.height
     97  );
     98  leftCtx.putImageData(video1Frame, 0, 0);
     99 
    100  let right = document.getElementById("right");
    101  let rightCtx = getWrappedScaledCanvasContext(
    102    right,
    103    video2Frame.width,
    104    video2Frame.height
    105  );
    106  rightCtx.putImageData(video2Frame, 0, 0);
    107 
    108  if (video1Frame.data.length != video2Frame.data.length) {
    109    return false;
    110  }
    111 
    112  let leftDataURL = left.toDataURL();
    113  let rightDataURL = right.toDataURL();
    114 
    115  if (leftDataURL != rightDataURL) {
    116    dump("Left frame: " + leftDataURL + "\n\n");
    117    dump("Right frame: " + rightDataURL + "\n\n");
    118 
    119    return false;
    120  }
    121 
    122  return true;
    123 }
    124 
    125 /**
    126 * Testing helper function that constructs a node clone of a video,
    127 * injects it into the DOM, and then runs an async testing function.
    128 * This also does the work of removing the clone before resolving.
    129 *
    130 * @param video (<video> element)
    131 *        The video to clone the node from.
    132 * @param asyncFn (async function)
    133 *        A test function that will be passed the new clone as its
    134 *        only argument.
    135 * @returns {Promise<void>}
    136 *   Resolves when the asyncFn has resolved and the clone has been removed from
    137 *   the DOM.
    138 */
    139 async function withNewClone(video, asyncFn) {
    140  let clone = video.cloneNode();
    141  clone.id = "clone";
    142  clone.src = "";
    143  let content = document.getElementById("content");
    144  content.appendChild(clone);
    145 
    146  try {
    147    await asyncFn(clone);
    148  } finally {
    149    clone.remove();
    150  }
    151 }
    152 
    153 /**
    154 * Sets the src on a video and waits until its ready.
    155 *
    156 * @param video (<video> element)
    157 *        The video to set the src on.
    158 * @param src (string)
    159 *        The URL to set as the source on a video.
    160 * @returns {Promise<Event>}
    161 *   Resolves when the video fires the "canplay" event.
    162 */
    163 async function setVideoSrc(video, src) {
    164  let promiseReady = waitForEventOnce(video, "canplay");
    165  video.src = src;
    166  await promiseReady;
    167 }
    168 
    169 /**
    170 * Returns a Promise once target emits a particular event
    171 * once.
    172 *
    173 * @param target (DOM node)
    174 *        The target to monitor for the event.
    175 * @param event (string)
    176 *        The name of the event to wait for.
    177 * @returns {Promise<Event>}
    178 *   Resolves when the event fires with the event.
    179 */
    180 function waitForEventOnce(target, event) {
    181  return new Promise(resolve => {
    182    target.addEventListener(event, resolve, { once: true });
    183  });
    184 }
    185 
    186 /**
    187 * Polls the video debug data as a hacky way of knowing when
    188 * when the decoders have shut down.
    189 *
    190 * @param video (<video> element)
    191 * @returns {Promise<void>}
    192 *   Resolves when the decoder has shut down.
    193 */
    194 function waitForShutdownDecoder(video) {
    195  return SimpleTest.promiseWaitForCondition(async () => {
    196    let readerData = await SpecialPowers.wrap(video).mozRequestDebugInfo();
    197    return readerData.decoder.reader.audioDecoderName == "shutdown";
    198  }, "Video decoder should eventually shut down.");
    199 }
    200 
    201 /**
    202 * Ensures that both hiding and pausing the video causes the
    203 * video to suspend and make dormant its decoders, respectively.
    204 *
    205 * @param video (<video element)
    206 */
    207 async function ensureVideoSuspendable(video) {
    208  video = SpecialPowers.wrap(video);
    209 
    210  ok(!video.hasSuspendTaint(), "Should be suspendable");
    211 
    212  // First, we'll simulate putting the video in the background by
    213  // making it invisible.
    214  let suspendPromise = waitForEventOnce(video, "mozentervideosuspend");
    215  video.setVisible(false);
    216  await suspendPromise;
    217  ok(true, "Suspended after the video was made invisible.");
    218  video.setVisible(true);
    219 
    220  ok(!video.hasSuspendTaint(), "Should still be suspendable.");
    221 
    222  // Next, we'll pause the video.
    223  await video.pause();
    224  await waitForShutdownDecoder(video);
    225  ok(true, "Shutdown decoder after the video was paused.");
    226  await video.play();
    227 }