tor-browser

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

mediasource.js (5910B)


      1 // Helpers for Media Source Extensions tests
      2 
      3 let gMSETestPrefs = [
      4  ["media.mediasource.enabled", true],
      5  ["media.audio-max-decode-error", 0],
      6  ["media.video-max-decode-error", 0],
      7 ];
      8 
      9 // Called before runWithMSE() to set the prefs before running MSE tests.
     10 function addMSEPrefs(...prefs) {
     11  gMSETestPrefs = gMSETestPrefs.concat(prefs);
     12 }
     13 
     14 async function runWithMSE(testFunction) {
     15  await once(window, "load");
     16  await SpecialPowers.pushPrefEnv({ set: gMSETestPrefs });
     17 
     18  const ms = new MediaSource();
     19 
     20  const el = document.createElement("video");
     21  el.src = URL.createObjectURL(ms);
     22  el.preload = "auto";
     23 
     24  document.body.appendChild(el);
     25  SimpleTest.registerCleanupFunction(() => {
     26    el.remove();
     27    el.removeAttribute("src");
     28    el.load();
     29  });
     30  try {
     31    await testFunction(ms, el);
     32  } catch (e) {
     33    ok(false, `${testFunction.name} failed with error ${e.name}`);
     34    throw e;
     35  }
     36 }
     37 
     38 async function fetchWithXHR(uri) {
     39  return new Promise(resolve => {
     40    const xhr = new XMLHttpRequest();
     41    xhr.open("GET", uri, true);
     42    xhr.responseType = "arraybuffer";
     43    xhr.addEventListener("load", function () {
     44      is(
     45        xhr.status,
     46        200,
     47        "fetchWithXHR load uri='" + uri + "' status=" + xhr.status
     48      );
     49      resolve(xhr.response);
     50    });
     51    xhr.send();
     52  });
     53 }
     54 
     55 function range(start, end) {
     56  const rv = [];
     57  for (let i = start; i < end; ++i) {
     58    rv.push(i);
     59  }
     60  return rv;
     61 }
     62 
     63 function must_throw(f, msg, error = true) {
     64  try {
     65    f();
     66    ok(!error, msg);
     67  } catch (e) {
     68    ok(error, msg);
     69    if (error === true) {
     70      ok(
     71        false,
     72        `Please provide name of expected error! Got ${e.name}: ${e.message}.`
     73      );
     74    } else if (e.name != error) {
     75      throw e;
     76    }
     77  }
     78 }
     79 
     80 async function must_reject(f, msg, error = true) {
     81  try {
     82    await f();
     83    ok(!error, msg);
     84  } catch (e) {
     85    ok(error, msg);
     86    if (error === true) {
     87      ok(
     88        false,
     89        `Please provide name of expected error! Got ${e.name}: ${e.message}.`
     90      );
     91    } else if (e.name != error) {
     92      throw e;
     93    }
     94  }
     95 }
     96 
     97 const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
     98 
     99 const must_not_throw = (f, msg) => must_throw(f, msg, false);
    100 const must_not_reject = (f, msg) => must_reject(f, msg, false);
    101 
    102 async function once(target, name) {
    103  return new Promise(r => target.addEventListener(name, r, { once: true }));
    104 }
    105 
    106 function timeRangeToString(r) {
    107  let str = "TimeRanges: ";
    108  for (let i = 0; i < r.length; i++) {
    109    str += "[" + r.start(i) + ", " + r.end(i) + ")";
    110  }
    111  return str;
    112 }
    113 
    114 async function loadSegment(sb, typedArrayOrArrayBuffer) {
    115  const typedArray =
    116    typedArrayOrArrayBuffer instanceof ArrayBuffer
    117      ? new Uint8Array(typedArrayOrArrayBuffer)
    118      : typedArrayOrArrayBuffer;
    119  info(
    120    `Loading buffer: [${typedArray.byteOffset}, ${
    121      typedArray.byteOffset + typedArray.byteLength
    122    })`
    123  );
    124  const beforeBuffered = timeRangeToString(sb.buffered);
    125  const p = once(sb, "update");
    126  sb.appendBuffer(typedArray);
    127  await p;
    128  const afterBuffered = timeRangeToString(sb.buffered);
    129  info(
    130    `SourceBuffer buffered ranges grew from ${beforeBuffered} to ${afterBuffered}`
    131  );
    132 }
    133 
    134 async function fetchAndLoad(sb, prefix, chunks, suffix) {
    135  // Fetch the buffers in parallel.
    136  const buffers = await Promise.all(
    137    chunks.map(c => fetchWithXHR(prefix + c + suffix))
    138  );
    139 
    140  // Load them in series, as required per spec.
    141  for (const buffer of buffers) {
    142    await loadSegment(sb, buffer);
    143  }
    144 }
    145 
    146 function loadSegmentAsync(sb, typedArrayOrArrayBuffer) {
    147  const typedArray =
    148    typedArrayOrArrayBuffer instanceof ArrayBuffer
    149      ? new Uint8Array(typedArrayOrArrayBuffer)
    150      : typedArrayOrArrayBuffer;
    151  info(
    152    `Loading buffer2: [${typedArray.byteOffset}, ${
    153      typedArray.byteOffset + typedArray.byteLength
    154    })`
    155  );
    156  const beforeBuffered = timeRangeToString(sb.buffered);
    157  return sb.appendBufferAsync(typedArray).then(() => {
    158    const afterBuffered = timeRangeToString(sb.buffered);
    159    info(
    160      `SourceBuffer buffered ranges grew from ${beforeBuffered} to ${afterBuffered}`
    161    );
    162  });
    163 }
    164 
    165 function fetchAndLoadAsync(sb, prefix, chunks, suffix) {
    166  // Fetch the buffers in parallel.
    167  const buffers = {};
    168  const fetches = [];
    169  for (const chunk of chunks) {
    170    fetches.push(
    171      fetchWithXHR(prefix + chunk + suffix).then(
    172        ((c, x) => (buffers[c] = x)).bind(null, chunk)
    173      )
    174    );
    175  }
    176 
    177  // Load them in series, as required per spec.
    178  return Promise.all(fetches).then(function () {
    179    let rv = Promise.resolve();
    180    for (const chunk of chunks) {
    181      rv = rv.then(loadSegmentAsync.bind(null, sb, buffers[chunk]));
    182    }
    183    return rv;
    184  });
    185 }
    186 
    187 // Register timeout function to dump debugging logs.
    188 SimpleTest.registerTimeoutFunction(async function () {
    189  for (const v of document.getElementsByTagName("video")) {
    190    console.log(await SpecialPowers.wrap(v).mozRequestDebugInfo());
    191  }
    192  for (const a of document.getElementsByTagName("audio")) {
    193    console.log(await SpecialPowers.wrap(a).mozRequestDebugInfo());
    194  }
    195 });
    196 
    197 async function waitUntilTime(target, targetTime) {
    198  await new Promise(resolve => {
    199    target.addEventListener("waiting", function onwaiting() {
    200      info("Got a waiting event at " + target.currentTime);
    201      if (target.currentTime >= targetTime) {
    202        target.removeEventListener("waiting", onwaiting);
    203        resolve();
    204      }
    205    });
    206  });
    207  ok(true, "Reached target time of: " + targetTime);
    208 }
    209 
    210 // Log events for debugging.
    211 
    212 function logEvents(el) {
    213  [
    214    "suspend",
    215    "play",
    216    "canplay",
    217    "canplaythrough",
    218    "loadstart",
    219    "loadedmetadata",
    220    "loadeddata",
    221    "playing",
    222    "ended",
    223    "error",
    224    "stalled",
    225    "emptied",
    226    "abort",
    227    "waiting",
    228    "pause",
    229    "durationchange",
    230    "seeking",
    231    "seeked",
    232  ].forEach(type =>
    233    el.addEventListener(type, e => info(`got ${e.type} event`))
    234  );
    235 }