tor-browser

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

Frame-onStep-async-02.js (3373B)


      1 // With enough hackery, stepping in and out of async functions can be made to
      2 // work as users expect.
      3 //
      4 // This test exercises the common case when we have syntactically `await
      5 // $ASYNC_FN($ARGS)` so that the calls nest as if they were synchronous
      6 // calls. It works, but there's a problem.
      7 //
      8 // onStep fires in extra places that end users would find very confusing--see
      9 // the comment marked (!) below. As a result, Debugger API consumers must do
     10 // some extra work to skip pausing there. This test is a proof of concept that
     11 // shows what sort of effort is needed. It maintains a single `asyncStack` and
     12 // skips the onStep hook if we're not running the function at top of the async
     13 // stack. Real debuggers would have to maintain multiple async stacks.
     14 
     15 // Set up debuggee.
     16 var g = newGlobal({newCompartment: true});
     17 g.eval(`\
     18 async function outer() {                                // line 1
     19    return (await inner()) + (await inner()) + "!";     // 2
     20 }                                                       // 3
     21 async function inner() {                                // 4
     22    return (await leaf()) + (await leaf());             // 5
     23 }                                                       // 6
     24 async function leaf() {                                 // 7
     25    return (await Promise.resolve("m"));                // 8
     26 }                                                       // 9
     27 `);
     28 
     29 // Set up debugger.
     30 let previousLine = -1;
     31 let dbg = new Debugger(g);
     32 let log = "";
     33 let asyncStack = [];
     34 
     35 dbg.onEnterFrame = frame => {
     36    assertEq(frame.type, "call");
     37 
     38    // If we're entering this frame for the first time, push it to the async
     39    // stack.
     40    if (!frame.seen) {
     41        frame.seen = true;
     42        asyncStack.push(frame);
     43        log += "(";
     44    }
     45 
     46    frame.onStep = () => {
     47        // When stepping, we sometimes pause at opcodes in older frames (!)
     48        // where all that's happening is async function administrivia.
     49        //
     50        // For example, the first time `leaf()` yields, `inner()` and
     51        // `outer()` are still on the stack; they haven't awaited yet because
     52        // control has not returned from `leaf()` to them yet. So stepping will
     53        // hop from line 8 to line 5 to line 2 as we unwind the stack, then
     54        // resume on line 8.
     55        //
     56        // Anyway: skip that noise.
     57        if (frame !== asyncStack[asyncStack.length - 1])
     58            return;
     59 
     60        let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
     61        if (previousLine != line) {
     62            log += line; // We stepped to a new line.
     63            previousLine = line;
     64        }
     65    };
     66 
     67    frame.onPop = completion => {
     68        // Popping the frame. But async function frames are popped multiple
     69        // times: for the "initial suspend", at each await, and on return. The
     70        // debugger offers no easy way to distinguish them (bug 1470558).
     71        // For now there's an "await" property, but bug 1470558 may come up
     72        // with a different solution, so don't rely on it!
     73        if (!completion.await) {
     74            // Returning (not awaiting or at initial suspend).
     75            assertEq(asyncStack.pop(), frame);
     76            log += ")";
     77        }
     78    };
     79 };
     80 
     81 // Run.
     82 let result;
     83 g.outer().then(v => { result = v; });
     84 drainJobQueue();
     85 
     86 assertEq(result, "mmmm!");
     87 assertEq(log, "(12(45(789)5(789)56)2(45(789)5(789)56)23)");