// onStep hooks on suspended Frames can keep Debuggers alive, even chaining them.

// The path through the heap we're building and testing here is:
//     gen0 (generator object) -> frame1 (suspended Frame with .onStep) -> dbg1 (Debugger object)
//       -> gen1 -> frame2 -> dbg2
// where everything after `gen1` is otherwise unreachable, and the edges
// `frame1 -> dbg1` and `frames2 -> dbg2` are due to the .onStep handlers, not
// strong refrences.
//
// There is no easy way to thread an event through this whole path; when we
// call gen0.next(), it will fire frame1.onStep(), but from there, making sure
// gen1.next() is called requires some minor heroics (see the WeakMap below).

var gen0;

var hits2 = 0;
var resuming2 = false;

function onStep2() {
    if (resuming2) {
        hits2++;
        resuming2 = false;
    }
}

function setup() {
    let g1 = newGlobal({newCompartment: true});
    g1.eval(`
        function* gf1() {
             debugger;
             yield 1;
             return 'done';
        }
    `);
    gen0 = g1.gf1();

    let g2 = newGlobal({newCompartment: true});
    g2.eval(`
        function* gf2() { debugger; yield 1; return 'done'; }

        var resuming1 = false;

        function makeOnStepHook1(dbg1) {
            // We use this WeakMap as a weak reference from frame1.onStep to dbg1.
            var weak = new WeakMap();
            weak.set(dbg1, {});
            return () => {
                if (resuming1) {
                    var dbg1Arr = nondeterministicGetWeakMapKeys(weak);
                    assertEq(dbg1Arr.length, 1);
                    dbg1Arr[0].gen1.next();
                    resuming1 = false;
                }
            };
        }

        function test(g1, gen0) {
            let dbg1 = Debugger(g1);
            dbg1.onDebuggerStatement = frame1 => {
                frame1.onStep = makeOnStepHook1(dbg1);
                dbg1.onDebuggerStatement = undefined;
            };
            gen0.next();  // run to yield point, creating frame1 and setting its onStep hook
            resuming1 = true;
            dbg1.gen1 = gf2();
            return dbg1.gen1;
        }
    `);

    let dbg2 = Debugger(g2);
    dbg2.onDebuggerStatement = frame2 => {
        frame2.onStep = onStep2;
        dbg2.onDebuggerStatement = undefined;
    };
    var gen1 = g2.test(g1, gen0);
    gen1.next();  // run to yield point, creating frame2 and setting its onStep hook
    resuming2 = true;
}

setup();
gc();
assertEq(hits2, 0);
gen0.next();  // fires frame1.onStep, which calls gen1.next(), which fires frame2.onStep
assertEq(hits2, 1);