// Test that SavedFrame.prototype.parent gives the next older frame whose
// principals are subsumed by the caller's principals.

// Given a string of letters |expected|, say "abc", assert that the stack
// contains calls to a series of functions named by the next letter from
// the string, say a, b, and then c. Younger frames appear earlier in
// |expected| than older frames.
function check(expected, stack) {
  print("check(" + JSON.stringify(expected) + ") against:\n" + stack);
  count++;

  while (stack.length && expected.length) {
    assertEq(stack.shift(), expected[0]);
    expected = expected.slice(1);
  }

  if (expected.length > 0) {
    throw new Error("Missing frames for: " + expected);
  }
  if (stack.length > 0 && !stack.every(s => s === null)) {
    throw new Error("Unexpected extra frame(s):\n" + stack);
  }
}

// Go from a SavedFrame linked list to an array of function display names.
function extract(stack) {
  const results = [];
  while (stack) {
    results.push(stack.functionDisplayName);
    stack = stack.parent;
  }
  return results;
}

const low  = newGlobal({ principal: 0       });
const mid  = newGlobal({ principal: 0xffff  });
const high = newGlobal({ principal: 0xfffff });

var count = 0;

     eval('function a() { check("a",        extract(saveStack())); b(); }');
low .eval('function b() { check("b",        extract(saveStack())); c(); }');
mid .eval('function c() { check("cba",      extract(saveStack())); d(); }');
high.eval('function d() { check("dcba",     extract(saveStack())); e(); }');
     eval('function e() { check("ecba",     extract(saveStack())); f(); }');
low .eval('function f() { check("fb",       extract(saveStack())); g(); }');
mid .eval('function g() { check("gfecba",   extract(saveStack())); h(); }');
high.eval('function h() { check("hgfedcba", extract(saveStack()));      }');

// Make everyone's functions visible to each other, as needed.
     b = low .b;
low .c = mid .c;
mid .d = high.d;
high.e =      e;
     f = low .f;
low .g = mid .g;
mid .h = high.h;

low.check = mid.check = high.check = check;

// They each must have their own extract so that CCWs don't mess up the
// principals when we ask for the parent property.
low. eval("" + extract);
mid. eval("" + extract);
high.eval("" + extract);

// Kick the whole process off.
a();

assertEq(count, 8);
