tor-browser

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

mediasource-worker-handle-transfer.html (12426B)


      1 <!DOCTYPE html>
      2 <html>
      3 <title>Test MediaSourceHandle transfer characteristics</title>
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <script src="mediasource-message-util.js"></script>
      7 <body>
      8 <script>
      9 
     10 function assert_mseiw_supported() {
     11  // Fail fast if MSE-in-Workers is not supported.
     12  assert_true(
     13      MediaSource.hasOwnProperty('canConstructInDedicatedWorker'),
     14      'MediaSource hasOwnProperty \'canConstructInDedicatedWorker\'');
     15  assert_true(
     16      MediaSource.canConstructInDedicatedWorker,
     17      'MediaSource.canConstructInDedicatedWorker');
     18  assert_true(
     19      window.hasOwnProperty('MediaSourceHandle'),
     20      'window must have MediaSourceHandle visibility');
     21 }
     22 
     23 function get_handle_from_new_worker(
     24    t, script = 'mediasource-worker-handle-transfer-to-main.js') {
     25  return new Promise((r) => {
     26    let worker = new Worker(script);
     27    worker.addEventListener('message', t.step_func(e => {
     28      let subject = e.data.subject;
     29      assert_true(subject != undefined, 'message must have a subject field');
     30      switch (subject) {
     31        case messageSubject.ERROR:
     32          assert_unreached('Worker error: ' + e.data.info);
     33          break;
     34        case messageSubject.HANDLE:
     35          const handle = e.data.info;
     36          assert_not_equals(
     37              handle, null, 'must have a non-null MediaSourceHandle');
     38          r({worker, handle});
     39          break;
     40        default:
     41          assert_unreached('Unexpected message subject: ' + subject);
     42      }
     43    }));
     44  });
     45 }
     46 
     47 promise_test(async t => {
     48  assert_mseiw_supported();
     49  let {worker, handle} = await get_handle_from_new_worker(t);
     50  assert_true(
     51      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
     52  assert_throws_dom('DataCloneError', function() {
     53    worker.postMessage(handle);
     54  }, 'serializing handle without transfer');
     55 }, 'MediaSourceHandle serialization without transfer must fail, tested in window context');
     56 
     57 promise_test(async t => {
     58  assert_mseiw_supported();
     59  let {worker, handle} = await get_handle_from_new_worker(t);
     60  assert_true(
     61      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
     62  assert_throws_dom('DataCloneError', function() {
     63    worker.postMessage(handle, [handle, handle]);
     64  }, 'transferring same handle more than once in same postMessage');
     65 }, 'Same MediaSourceHandle transferred multiple times in single postMessage must fail, tested in window context');
     66 
     67 promise_test(async t => {
     68  assert_mseiw_supported();
     69  let {worker, handle} = await get_handle_from_new_worker(t);
     70  assert_true(
     71      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
     72 
     73  // Transferring handle to worker without including it in the message is still
     74  // a valid transfer, though the recipient will not be able to obtain the
     75  // handle itself. Regardless, the handle in this sender's context will be
     76  // detached.
     77  worker.postMessage(null, [handle]);
     78 
     79  assert_throws_dom('DataCloneError', function() {
     80    worker.postMessage(null, [handle]);
     81  }, 'transferring handle that was already detached should fail');
     82 
     83  assert_throws_dom('DataCloneError', function() {
     84    worker.postMessage(handle, [handle]);
     85  }, 'transferring handle that was already detached should fail, even if this time it\'s included in the message');
     86 }, 'Attempt to transfer detached MediaSourceHandle must fail, tested in window context');
     87 
     88 promise_test(async t => {
     89  assert_mseiw_supported();
     90  let {worker, handle} = await get_handle_from_new_worker(t);
     91  assert_true(
     92      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
     93 
     94  let video = document.createElement('video');
     95  document.body.appendChild(video);
     96  video.srcObject = handle;
     97 
     98  assert_throws_dom('DataCloneError', function() {
     99    worker.postMessage(handle, [handle]);
    100  }, 'transferring handle that is currently srcObject fails');
    101  assert_equals(video.srcObject, handle);
    102 
    103  // Clear |handle| from being the srcObject value.
    104  video.srcObject = null;
    105 
    106  assert_throws_dom('DataCloneError', function() {
    107    worker.postMessage(handle, [handle]);
    108  }, 'transferring handle that was briefly srcObject before srcObject was reset to null should also fail');
    109  assert_equals(video.srcObject, null);
    110 }, 'MediaSourceHandle cannot be transferred, immediately after set as srcObject, even if srcObject immediately reset to null');
    111 
    112 promise_test(async t => {
    113  assert_mseiw_supported();
    114  let {worker, handle} = await get_handle_from_new_worker(t);
    115  assert_true(
    116      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
    117 
    118  let video = document.createElement('video');
    119  document.body.appendChild(video);
    120  video.srcObject = handle;
    121  assert_not_equals(video.networkState, HTMLMediaElement.NETWORK_LOADING);
    122  // Initial step of resource selection algorithm sets networkState to
    123  // NETWORK_NO_SOURCE. networkState only becomes NETWORK_LOADING after stable
    124  // state awaited and resource selection algorithm continues with, in this
    125  // case, an assigned media provider object (which is the MediaSource
    126  // underlying the handle).
    127  assert_equals(video.networkState, HTMLMediaElement.NETWORK_NO_SOURCE);
    128 
    129  // Wait until 'loadstart' media element event is dispatched.
    130  await new Promise((r) => {
    131    video.addEventListener(
    132        'loadstart', t.step_func(e => {
    133          r();
    134        }),
    135        {once: true});
    136  });
    137  assert_equals(video.networkState, HTMLMediaElement.NETWORK_LOADING);
    138 
    139  assert_throws_dom('DataCloneError', function() {
    140    worker.postMessage(handle, [handle]);
    141  }, 'transferring handle that is currently srcObject, after loadstart, fails');
    142  assert_equals(video.srcObject, handle);
    143 
    144  // Clear |handle| from being the srcObject value.
    145  video.srcObject = null;
    146 
    147  assert_throws_dom('DataCloneError', function() {
    148    worker.postMessage(handle, [handle]);
    149  }, 'transferring handle that was srcObject until \'loadstart\' when srcObject was reset to null should also fail');
    150  assert_equals(video.srcObject, null);
    151 }, 'MediaSourceHandle cannot be transferred, if it was srcObject when asynchronous load starts (loadstart), even if srcObject is then immediately reset to null');
    152 
    153 promise_test(async t => {
    154  assert_mseiw_supported();
    155  let {worker, handle} = await get_handle_from_new_worker(t);
    156  assert_true(
    157      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
    158 
    159  let video = document.createElement('video');
    160  document.body.appendChild(video);
    161 
    162  // Transfer the handle away so that our instance of it is detached.
    163  worker.postMessage(null, [handle]);
    164 
    165  // Now assign handle to srcObject to attempt load. 'loadstart' event should
    166  // occur, but then media element error should occur due to failure to attach
    167  // to the underlying MediaSource of a detached MediaSourceHandle.
    168 
    169  video.srcObject = handle;
    170  assert_equals(
    171      video.networkState, HTMLMediaElement.NETWORK_NO_SOURCE,
    172      'before async load start, networkState should be NETWORK_NO_SOURCE');
    173 
    174  // Before 'loadstart' dispatch, we don't expect the media element error.
    175  video.onerror = t.unreached_func(
    176      'Error is unexpected before \'loadstart\' event dispatch');
    177 
    178  // Wait until 'loadstart' media element event is dispatched.
    179  await new Promise((r) => {
    180    video.addEventListener(
    181        'loadstart', t.step_func(e => {
    182          r();
    183        }),
    184        {once: true});
    185  });
    186 
    187  // Now wait until 'error' media element event is dispatched.
    188  video.onerror = null;
    189  await new Promise((r) => {
    190    video.addEventListener(
    191        'error', t.step_func(e => {
    192          r();
    193        }),
    194        {once: true});
    195  });
    196 
    197  // Confirm expected error and states resulting from the "dedicated media
    198  // source failure steps":
    199  // https://html.spec.whatwg.org/multipage/media.html#dedicated-media-source-failure-steps
    200  let e = video.error;
    201  assert_true(e instanceof MediaError);
    202  assert_equals(e.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
    203  assert_equals(
    204      video.readyState, HTMLMediaElement.HAVE_NOTHING,
    205      'load failure should occur long before parsing any appended metadata.');
    206  assert_equals(video.networkState, HTMLMediaElement.NETWORK_NO_SOURCE);
    207 
    208  // Even if the handle is detached and attempt to load it failed, the handle is
    209  // still detached, and as well, has also been used as srcObject now. Re-verify
    210  // that such a handle instance must fail transfer attempt.
    211  assert_throws_dom('DataCloneError', function() {
    212    worker.postMessage(handle, [handle]);
    213  }, 'transferring detached handle that is currently srcObject, after loadstart and load failure, fails');
    214  assert_equals(video.srcObject, handle);
    215 
    216  // Clear |handle| from being the srcObject value.
    217  video.srcObject = null;
    218 
    219  assert_throws_dom('DataCloneError', function() {
    220    worker.postMessage(handle, [handle]);
    221  }, 'transferring detached handle that was srcObject until \'loadstart\' and load failure when srcObject was reset to null should also fail');
    222  assert_equals(video.srcObject, null);
    223 }, 'A detached (already transferred away) MediaSourceHandle cannot successfully load when assigned to srcObject');
    224 
    225 promise_test(async t => {
    226  assert_mseiw_supported();
    227  // Get a handle from a worker that is prepared to buffer real media once its
    228  // MediaSource instance attaches and 'sourceopen' is dispatched. Unlike
    229  // earlier cases in this file, we need positive indication from precisely one
    230  // of multiple media elements that the attachment and playback succeeded.
    231  let {worker, handle} =
    232      await get_handle_from_new_worker(t, 'mediasource-worker-play.js');
    233  assert_true(
    234      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
    235 
    236  let videos = [];
    237  const NUM_ELEMENTS = 5;
    238  for (let i = 0; i < NUM_ELEMENTS; ++i) {
    239    let v = document.createElement('video');
    240    videos.push(v);
    241    document.body.appendChild(v);
    242  }
    243 
    244  await new Promise((r) => {
    245    let errors = 0;
    246    let endeds = 0;
    247 
    248    // Setup handlers to expect precisely 1 ended and N-1 errors.
    249    videos.forEach((v) => {
    250      v.addEventListener(
    251          'error', t.step_func(e => {
    252            // Confirm expected error and states resulting from the "dedicated
    253            // media source failure steps":
    254            // https://html.spec.whatwg.org/multipage/media.html#dedicated-media-source-failure-steps
    255            let err = v.error;
    256            assert_true(err instanceof MediaError);
    257            assert_equals(err.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
    258            assert_equals(
    259                v.readyState, HTMLMediaElement.HAVE_NOTHING,
    260                'load failure should occur long before parsing any appended metadata.');
    261            assert_equals(v.networkState, HTMLMediaElement.NETWORK_NO_SOURCE);
    262 
    263            errors++;
    264            if (errors + endeds == videos.length && endeds == 1)
    265              r();
    266          }),
    267          {once: true});
    268      v.addEventListener(
    269          'ended', t.step_func(e => {
    270            endeds++;
    271            if (errors + endeds == videos.length && endeds == 1)
    272              r();
    273          }),
    274          {once: true});
    275      v.srcObject = handle;
    276      assert_equals(
    277          v.networkState, HTMLMediaElement.NETWORK_NO_SOURCE,
    278          'before async load start, networkState should be NETWORK_NO_SOURCE');
    279    });
    280 
    281    let playPromises = [];
    282    videos.forEach((v) => {
    283      playPromises.push(v.play());
    284    });
    285 
    286    // Ignore playPromise success/rejection, if any.
    287    playPromises.forEach((p) => {
    288      if (p !== undefined) {
    289        p.then(_ => {}).catch(_ => {});
    290      }
    291    });
    292  });
    293 
    294  // Once the handle has been assigned as srcObject, it must fail transfer
    295  // steps.
    296  assert_throws_dom('DataCloneError', function() {
    297    worker.postMessage(handle, [handle]);
    298  }, 'transferring handle that is currently srcObject on multiple elements, fails');
    299  videos.forEach((v) => {
    300    assert_equals(v.srcObject, handle);
    301    v.srcObject = null;
    302  });
    303 
    304  assert_throws_dom('DataCloneError', function() {
    305    worker.postMessage(handle, [handle]);
    306  }, 'transferring handle that was srcObject on multiple elements, then was unset on them, should also fail');
    307  videos.forEach((v) => {
    308    assert_equals(v.srcObject, null);
    309  });
    310 }, 'Precisely one load of the same MediaSourceHandle assigned synchronously to multiple media element srcObjects succeeds');
    311 
    312 fetch_tests_from_worker(new Worker('mediasource-worker-handle-transfer.js'));
    313 
    314 </script>
    315 </body>
    316 </html>