tor-browser

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

mediasource-worker-duration.js (11249B)


      1 importScripts("mediasource-worker-util.js");
      2 
      3 // Note, we do not use testharness.js utilities within the worker context
      4 // because it also communicates using postMessage to the main HTML document's
      5 // harness, and would confuse the test case message parsing there.
      6 
      7 let util = new MediaSourceWorkerUtil();
      8 let sourceBuffer;
      9 
     10 // Phases of this test case, in sequence:
     11 const testPhase = {
     12  // Main thread verifies initial unattached HTMLMediaElement duration is NaN
     13  // and readyState is HAVE_NOTHING, then starts this worker.
     14  // This worker creates a MediaSource, verifies its initial duration
     15  // is NaN, creates an object URL for the MediaSource and sends the URL to the
     16  // main thread.
     17  kInitial: "Initial",
     18 
     19  // Main thread receives MediaSourceHandle, re-verifies that the media element
     20  // duration is still NaN and readyState is still HAVE_NOTHING, and then sets
     21  // the handle as the srcObject of the media element, eventually causing worker
     22  // mediaSource 'onsourceopen' event dispatch.
     23  kAttaching: "Awaiting sourceopen event that signals attachment is setup",
     24 
     25  kRequestNaNDurationCheck:
     26      "Sending request to main thread to verify expected duration of the freshly setup attachment",
     27  kConfirmNaNDurationResult:
     28      "Checking that main thread correctly ACK'ed the freshly setup attachment's duration verification request",
     29 
     30  kRequestHaveNothingReadyStateCheck:
     31      "Sending request to main thread to verify expected readyState of HAVE_NOTHING of the freshly setup attachment",
     32  kConfirmHaveNothingReadyStateResult:
     33      "Checking that main thread correctly ACK'ed the freshly setup attachment's readyState HAVE_NOTHING verification request",
     34 
     35  kRequestSetDurationCheck:
     36      "Sending request to main thread to verify explicitly set duration before any media data has been appended",
     37  kConfirmSetDurationResult:
     38      "Checking that main thread correctly ACK'ed the duration verification request of explicitly set duration before any media data has been appended",
     39 
     40  kRequestHaveNothingReadyStateRecheck:
     41      "Sending request to main thread to recheck that the readyState is still HAVE_NOTHING",
     42  kConfirmHaveNothingReadyStateRecheckResult:
     43      "Checking that main thread correctly ACK'ed the request to recheck readyState of HAVE_NOTHING",
     44 
     45  kRequestAwaitNewDurationCheck:
     46      "Buffering media and then sending request to main thread to await duration reaching the expected value due to buffering",
     47  kConfirmAwaitNewDurationResult:
     48      "Checking that main thread correctly ACK'ed the request to await duration reaching the expected value due to buffering",
     49 
     50  kRequestAtLeastHaveMetadataReadyStateCheck:
     51      "Sending request to main thread to verify expected readyState of at least HAVE_METADATA due to buffering",
     52  kConfirmAtLeastHaveMetadataReadyStateResult:
     53      "Checking that main thread correctly ACK'ed the request to verify expected readyState of at least HAVE_METADATA due to buffering",
     54 
     55 };
     56 
     57 let phase = testPhase.kInitial;
     58 
     59 // Setup handler for receipt of attachment completion.
     60 util.mediaSource.addEventListener("sourceopen", () => {
     61  assert(phase === testPhase.kAttaching, "Unexpected sourceopen received by Worker mediaSource.");
     62  phase = testPhase.kRequestNaNDurationCheck;
     63  processPhase();
     64 }, { once : true });
     65 
     66 // Setup handler for receipt of acknowledgement of successful verifications from
     67 // main thread. |ackVerificationData| contains the round-tripped verification
     68 // request that the main thread just sent, and is used in processPhase to ensure
     69 // the ACK for this phase matched the request for verification.
     70 let ackVerificationData;
     71 onmessage = e => {
     72  if (e.data === undefined || e.data.subject !== messageSubject.ACK_VERIFIED || e.data.info === undefined) {
     73    postMessage({
     74      subject: messageSubject.ERROR,
     75      info: "Invalid message received by Worker"
     76    });
     77    return;
     78  }
     79 
     80  ackVerificationData = e.data.info;
     81  processPhase(/* isResponseToAck */ true);
     82 };
     83 
     84 processPhase();
     85 
     86 
     87 // Returns true if checks succeed, false otherwise.
     88 function checkAckVerificationData(expectedRequest) {
     89 
     90  // Compares only subject and info fields. Uses logic similar to testharness.js's
     91  // same_value(x,y) to correctly handle NaN, but doesn't distinguish +0 from -0.
     92  function messageValuesEqual(m1, m2) {
     93    if (m1.subject !== m1.subject) {
     94      // NaN case
     95      if (m2.subject === m2.subject)
     96        return false;
     97    } else if (m1.subject !== m2.subject) {
     98      return false;
     99    }
    100 
    101    if (m1.info !== m1.info) {
    102      // NaN case
    103      return (m2.info !== m2.info);
    104    }
    105 
    106    return m1.info === m2.info;
    107  }
    108 
    109  if (messageValuesEqual(expectedRequest, ackVerificationData)) {
    110    ackVerificationData = undefined;
    111    return true;
    112  }
    113 
    114  postMessage({
    115    subject: messageSubject.ERROR,
    116    info: "ACK_VERIFIED message from main thread was for a mismatching request for this phase. phase=[" + phase +
    117          "], expected request that would produce ACK in this phase=[" + JSON.stringify(expectedRequest) +
    118          "], actual request reported with the ACK=[" + JSON.stringify(ackVerificationData) + "]"
    119  });
    120 
    121  ackVerificationData = undefined;
    122  return false;
    123 }
    124 
    125 function bufferMediaAndSendDurationVerificationRequest() {
    126  sourceBuffer = util.mediaSource.addSourceBuffer(util.mediaMetadata.type);
    127  sourceBuffer.onerror = (err) => {
    128    postMessage({ subject: messageSubject.ERROR, info: err });
    129  };
    130  sourceBuffer.onupdateend = () => {
    131    // Sanity check the duration.
    132    // Unnecessary for this buffering, except helps with test coverage.
    133    var duration = util.mediaSource.duration;
    134    if (isNaN(duration) || duration <= 0.0) {
    135      postMessage({
    136        subject: messageSubject.ERROR,
    137        info: "mediaSource.duration " + duration + " is not within expected range (0,1)"
    138      });
    139      return;
    140    }
    141 
    142    // Await the main thread media element duration matching the worker
    143    // mediaSource duration.
    144    postMessage(getAwaitCurrentDurationRequest());
    145  };
    146 
    147  util.mediaLoadPromise.then(mediaData => { sourceBuffer.appendBuffer(mediaData); },
    148                             err => { postMessage({ subject: messageSubject.ERROR, info: err }) });
    149 }
    150 
    151 
    152 function getAwaitCurrentDurationRequest() {
    153  // Sanity check that we have a numeric duration value now.
    154  const dur = util.mediaSource.duration;
    155  assert(!Number.isNaN(dur), "Unexpected NaN duration in worker");
    156  return { subject: messageSubject.AWAIT_DURATION, info: dur };
    157 }
    158 
    159 function assert(conditionBool, description) {
    160  if (conditionBool !== true) {
    161    postMessage({
    162      subject: messageSubject.ERROR,
    163      info: "Current test phase [" + phase + "] failed worker assertion. " + description
    164    });
    165  }
    166 }
    167 
    168 function processPhase(isResponseToAck = false) {
    169  assert(!isResponseToAck || (phase !== testPhase.kInitial && phase !== testPhase.kAttaching),
    170      "Phase does not expect verification ack receipt from main thread");
    171 
    172  // Some static request messages useful in transmission and ACK verification.
    173  const nanDurationCheckRequest = { subject: messageSubject.VERIFY_DURATION, info: NaN };
    174  const haveNothingReadyStateCheckRequest = { subject: messageSubject.VERIFY_HAVE_NOTHING };
    175  const setDurationCheckRequest = { subject: messageSubject.AWAIT_DURATION, info: 0.1 };
    176  const atLeastHaveMetadataReadyStateCheckRequest = { subject: messageSubject.VERIFY_AT_LEAST_HAVE_METADATA };
    177 
    178  switch (phase) {
    179 
    180    case testPhase.kInitial:
    181      assert(Number.isNaN(util.mediaSource.duration), "Initial unattached MediaSource duration must be NaN, but instead is " + util.mediaSource.duration);
    182      phase = testPhase.kAttaching;
    183      let handle = util.mediaSource.handle;
    184      postMessage({ subject: messageSubject.HANDLE, info: handle }, { transfer: [handle] } );
    185      break;
    186 
    187    case testPhase.kAttaching:
    188      postMessage({
    189        subject: messageSubject.ERROR,
    190        info: "kAttaching phase is handled by main thread and by worker onsourceopen, not this switch case."
    191      });
    192      break;
    193 
    194    case testPhase.kRequestNaNDurationCheck:
    195      assert(!isResponseToAck);
    196      postMessage(nanDurationCheckRequest);
    197      phase = testPhase.kConfirmNaNDurationResult;
    198      break;
    199 
    200    case testPhase.kConfirmNaNDurationResult:
    201      assert(isResponseToAck);
    202      if (checkAckVerificationData(nanDurationCheckRequest)) {
    203        phase = testPhase.kRequestHaveNothingReadyStateCheck;
    204        processPhase();
    205      }
    206      break;
    207 
    208    case testPhase.kRequestHaveNothingReadyStateCheck:
    209      assert(!isResponseToAck);
    210      postMessage(haveNothingReadyStateCheckRequest);
    211      phase = testPhase.kConfirmHaveNothingReadyStateResult;
    212      break;
    213 
    214    case testPhase.kConfirmHaveNothingReadyStateResult:
    215      assert(isResponseToAck);
    216      if (checkAckVerificationData(haveNothingReadyStateCheckRequest)) {
    217        phase = testPhase.kRequestSetDurationCheck;
    218        processPhase();
    219      }
    220      break;
    221 
    222    case testPhase.kRequestSetDurationCheck:
    223      assert(!isResponseToAck);
    224      const newDuration = setDurationCheckRequest.info;
    225      assert(!Number.isNaN(newDuration) && newDuration > 0);
    226 
    227      // Set the duration, then request verification.
    228      util.mediaSource.duration = newDuration;
    229      postMessage(setDurationCheckRequest);
    230      phase = testPhase.kConfirmSetDurationResult;
    231      break;
    232 
    233    case testPhase.kConfirmSetDurationResult:
    234      assert(isResponseToAck);
    235      if (checkAckVerificationData(setDurationCheckRequest)) {
    236        phase = testPhase.kRequestHaveNothingReadyStateRecheck;
    237        processPhase();
    238      }
    239      break;
    240 
    241    case testPhase.kRequestHaveNothingReadyStateRecheck:
    242      assert(!isResponseToAck);
    243      postMessage(haveNothingReadyStateCheckRequest);
    244      phase = testPhase.kConfirmHaveNothingReadyStateRecheckResult;
    245      break;
    246 
    247    case testPhase.kConfirmHaveNothingReadyStateRecheckResult:
    248      assert(isResponseToAck);
    249      if (checkAckVerificationData(haveNothingReadyStateCheckRequest)) {
    250        phase = testPhase.kRequestAwaitNewDurationCheck;
    251        processPhase();
    252      }
    253      break;
    254 
    255    case testPhase.kRequestAwaitNewDurationCheck:
    256      assert(!isResponseToAck);
    257      bufferMediaAndSendDurationVerificationRequest();
    258      phase = testPhase.kConfirmAwaitNewDurationResult;
    259      break;
    260 
    261    case testPhase.kConfirmAwaitNewDurationResult:
    262      assert(isResponseToAck);
    263      if (checkAckVerificationData(getAwaitCurrentDurationRequest())) {
    264        phase = testPhase.kRequestAtLeastHaveMetadataReadyStateCheck;
    265        processPhase();
    266      }
    267      break;
    268 
    269    case testPhase.kRequestAtLeastHaveMetadataReadyStateCheck:
    270      assert(!isResponseToAck);
    271      postMessage(atLeastHaveMetadataReadyStateCheckRequest);
    272      phase = testPhase.kConfirmAtLeastHaveMetadataReadyStateResult;
    273      break;
    274 
    275    case testPhase.kConfirmAtLeastHaveMetadataReadyStateResult:
    276      assert(isResponseToAck);
    277      if (checkAckVerificationData(atLeastHaveMetadataReadyStateCheckRequest)) {
    278        postMessage({ subject: messageSubject.WORKER_DONE });
    279      }
    280      phase = "No further phase processing should occur once WORKER_DONE message has been sent";
    281      break;
    282 
    283    default:
    284      postMessage({
    285        subject: messageSubject.ERROR,
    286        info: "Unexpected test phase in worker:" + phase,
    287      });
    288  }
    289 
    290 }