tor-browser

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

test_promises_run_to_completion.js (4438B)


      1 // Bug 1145201:  Promise then-handlers can still be executed while the debugger is paused.
      2 //
      3 // When a promise is resolved, for each of its callbacks, a microtask is queued
      4 // to run the callback. At various points, the HTML spec says the browser must
      5 // "perform a microtask checkpoint", which means to draw microtasks from the
      6 // queue and run them, until the queue is empty.
      7 //
      8 // The HTML spec is careful to perform a microtask checkpoint directly after
      9 // each invocation of an event handler or DOM callback, so that code using
     10 // promises can trust that its promise callbacks run promptly, in a
     11 // deterministic order, without DOM events or other outside influences
     12 // intervening.
     13 //
     14 // When the JavaScript debugger interrupts the execution of debuggee content
     15 // code, it naturally must process events for its own user interface and promise
     16 // callbacks. However, it must not run any debuggee microtasks. The debuggee has
     17 // been interrupted in the midst of executing some other code, and the
     18 // JavaScript spec promises developers: "Once execution of a Job is initiated,
     19 // the Job always executes to completion. No other Job may be initiated until
     20 // the currently running Job completes." [1] This promise would be broken if the
     21 // debugger's own event processing ran debuggee microtasks during the
     22 // interruption.
     23 //
     24 // Looking at things from the other side, a microtask checkpoint must be
     25 // performed before returning from a debugger callback, rather than being put
     26 // off until the debuggee performs its next microtask checkpoint, so that
     27 // debugger microtasks are not interleaved with debuggee microtasks. A debuggee
     28 // microtask could hit a breakpoint or otherwise re-enter the debugger, which
     29 // might be quite surprised to see a new debugger callback begin before its
     30 // previous promise callbacks could finish.
     31 //
     32 // [1]: https://www.ecma-international.org/ecma-262/9.0/index.html#sec-jobs-and-job-queues
     33 
     34 "use strict";
     35 
     36 const Debugger = require("Debugger");
     37 
     38 function test_promises_run_to_completion() {
     39  const g = createTestGlobal(
     40    "test global for test_promises_run_to_completion.js"
     41  );
     42  const dbg = new Debugger(g);
     43  g.Assert = Assert;
     44  const log = [""];
     45  g.log = log;
     46 
     47  dbg.onDebuggerStatement = function handleDebuggerStatement() {
     48    dbg.onDebuggerStatement = undefined;
     49 
     50    // Exercise the promise machinery: resolve a promise and perform a microtask
     51    // queue. When called from a debugger hook, the debuggee's microtasks should not
     52    // run.
     53    log[0] += "debug-handler(";
     54    Promise.resolve(42).then(v => {
     55      Assert.equal(
     56        v,
     57        42,
     58        "debugger callback promise handler got the right value"
     59      );
     60      log[0] += "debug-react";
     61    });
     62    log[0] += "(";
     63    force_microtask_checkpoint();
     64    log[0] += ")";
     65 
     66    Promise.resolve(42).then(() => {
     67      // The microtask running this callback should be handled as we leave the
     68      // onDebuggerStatement Debugger callback, and should not be interleaved
     69      // with debuggee microtasks.
     70      log[0] += "(trailing)";
     71    });
     72 
     73    log[0] += ")";
     74  };
     75 
     76  // Evaluate some debuggee code that resolves a promise, and then enters the debugger.
     77  Cu.evalInSandbox(
     78    `
     79    log[0] += "eval(";
     80    Promise.resolve(42).then(function debuggeePromiseCallback(v) {
     81      Assert.equal(v, 42, "debuggee promise handler got the right value");
     82      // Debugger microtask checkpoints must not run debuggee microtasks, so
     83      // this callback should run at the next microtask checkpoint *not*
     84      // performed by the debugger.
     85      log[0] += "eval-react";
     86    });
     87 
     88    log[0] += "debugger(";
     89    debugger;
     90    log[0] += "))";
     91  `,
     92    g
     93  );
     94 
     95  // Let other microtasks run. This should run the debuggee's promise callback.
     96  log[0] += "final(";
     97  force_microtask_checkpoint();
     98  log[0] += ")";
     99 
    100  Assert.equal(
    101    log[0],
    102    `\
    103 eval(\
    104 debugger(\
    105 debug-handler(\
    106 (debug-react)\
    107 )\
    108 (trailing)\
    109 ))\
    110 final(\
    111 eval-react\
    112 )`,
    113    "microtasks ran as expected"
    114  );
    115 
    116  run_next_test();
    117 }
    118 
    119 function force_microtask_checkpoint() {
    120  // Services.tm.spinEventLoopUntilEmpty only performs a microtask checkpoint if
    121  // there is actually an event to run. So make one up.
    122  let ran = false;
    123  Services.tm.dispatchToMainThread(() => {
    124    ran = true;
    125  });
    126  Services.tm.spinEventLoopUntil(
    127    "Test(test_promises_run_to_completion.js:force_microtask_checkpoint)",
    128    () => ran
    129  );
    130 }
    131 
    132 add_test(test_promises_run_to_completion);