// Test 'compartment revived' GCs, where we do an extra GC if there are
// compartments which we expected to die but were kept alive.

// A global used as the destination for transplants.
let transplantTargetGlobal = newGlobal();

function didCompartmentRevivedGC() {
  return performance.mozMemory.gc.lastStartReason === "COMPARTMENT_REVIVED";
}

function compartmentCount() {
  let r = performance.mozMemory.gc.compartmentCount;
  return r;
}

function startIncrementalGC() {
  startgc(1);
  while (gcstate() === "Prepare" || gcstate() == "MarkRoots") {
    gcslice(100, {dontStart: true});
  }
  assertEq(gcstate(), "Mark");
}

function finishIncrementalGC() {
  while (gcstate() !== "NotActive") {
    gcslice(100, {dontStart: true});
  }
  assertEq(gcstate(), "NotActive");
}

// Create a new compartment and global and return the global.
function createCompartment() {
  return newGlobal({newCompartment: true});
}

// Create a transplantable object and create a wrapper to it from a new
// compartment. Return a function to transplant the target object.
function createTransplantableWrapperTarget(wrapperGlobal) {
  let {object: target, transplant} = transplantableObject();
  wrapperGlobal.wrapper = target;
  return transplant;
}

// Transplant an object to a new global by calling the transplant
// function. This remaps all wrappers pointing to the target object,
// potentially keeping dead compartments alive.
function transplantTargetAndRemapWrappers(transplant) {
  transplant(transplantTargetGlobal);
}

// Test no compartment revived GC triggered in normal cases.
function testNormal() {
  gc();
  assertEq(didCompartmentRevivedGC(), false);

  startIncrementalGC();
  finishIncrementalGC();
  assertEq(didCompartmentRevivedGC(), false);

  let initialCount = compartmentCount();
  createCompartment();
  startIncrementalGC();
  finishIncrementalGC();
  assertEq(compartmentCount(), initialCount);
}

// Test compartment revived GC is triggered by wrapper remapping.
function testCompartmentRevived1() {
  let initialCount = compartmentCount();
  let compartment = createCompartment();
  let transplant = createTransplantableWrapperTarget(compartment);
  compartment = null;

  startIncrementalGC();
  transplantTargetAndRemapWrappers(transplant);
  finishIncrementalGC();

  assertEq(didCompartmentRevivedGC(), true);
  assertEq(compartmentCount(), initialCount);
}

// Test no compartment revived GC is triggered for compartments transitively
// kept alive by black roots.
function testCompartmentRevived2() {
  let initialCount = compartmentCount();
  let compartment = createCompartment();
  let transplant = createTransplantableWrapperTarget(compartment);
  let liveCompartment = createCompartment();
  liveCompartment.wrapper = compartment;
  compartment = null;

  startIncrementalGC();
  transplantTargetAndRemapWrappers(transplant);
  finishIncrementalGC();

  assertEq(didCompartmentRevivedGC(), false);
  assertEq(compartmentCount(), initialCount + 2);

  liveCompartment = null;
  gc();

  assertEq(compartmentCount(), initialCount);
}

// Test no compartment revived GC is triggered for compartments transitively
// kept alive by gray roots.
function testCompartmentRevived3() {
  let initialCount = compartmentCount();
  let compartment = createCompartment();
  let transplant = createTransplantableWrapperTarget(compartment);
  let liveCompartment = createCompartment();
  liveCompartment.wrapper = compartment;
  liveCompartment.eval('grayRoot()[0] = this');
  liveCompartment = null;
  gc();

  startIncrementalGC();
  transplantTargetAndRemapWrappers(transplant);
  finishIncrementalGC();

  assertEq(didCompartmentRevivedGC(), false);
  assertEq(compartmentCount(), initialCount + 2);

  // There's no easy way to clear gray roots for a compartment we don't have
  // any reference to.
}

gczeal(0);

testNormal();
testCompartmentRevived1();
testCompartmentRevived2();
testCompartmentRevived3();
