// |reftest| skip-if(!xulRuntime.shell)
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/

//-----------------------------------------------------------------------------
var BUGNUMBER = 1337209;
var summary =
  "Test gray marking";

print(BUGNUMBER + ": " + summary);

if (typeof gczeal !== 'undefined')
    gczeal(0);

function test() {
  grayRoot().x = Object.create(null);
  addMarkObservers([grayRoot(), grayRoot().x, this, Object.create(null)]);
  gc();
  let marks = getMarks();
  assertEq(marks[0], 'gray', 'gray root');
  assertEq(marks[1], 'gray', 'object reachable from gray root');
  assertEq(marks[2], 'black', 'global');
  assertEq(marks[3], 'dead', 'dead object should have been collected');

  grayRoot().x = 7; // Overwrite the object
  gc();
  marks = getMarks();
  assertEq(marks[0], 'gray', 'gray root');
  assertEq(marks[1], 'dead', 'object no longer reachable from gray root');
  assertEq(marks[2], 'black', 'global');
  assertEq(marks[3], 'dead', 'dead object should have been collected');

  var wm = new WeakMap();
  var global = newGlobal({newCompartment: true});

  var wrapper1 = global.eval("Object.create(null)");
  wrapper1.name = "wrapper1";
  var value1 = Object.create(null);
  wm.set(wrapper1, value1);

  var wrapper2 = global.eval("Object.create(null)");
  wrapper2.name = "wrapper2";
  var value2 = global.eval("Object.create(null)");
  wm.set(wrapper2, value2);

  grayRoot().root1 = wrapper1;
  grayRoot().root2 = wrapper2;
  clearMarkObservers();
  addMarkObservers([wrapper1, value1, wrapper2, value2]);
  wrapper1 = wrapper2 = null;
  value1 = value2 = null;
  gc();
  marks = getMarks();
  assertEq(marks[0], 'gray', 'gray key 1');
  assertEq(marks[1], 'gray', 'black map, gray key => gray value');
  assertEq(marks[2], 'gray', 'gray key 2');
  assertEq(marks[3], 'gray', 'black map, gray key => gray value');

  // Blacken one of the keys
  wrapper1 = grayRoot().root1;
  gc();
  marks = getMarks();
  assertEq(marks[0], 'black', 'black key 1');
  assertEq(marks[1], 'black', 'black map, black key => black value');
  assertEq(marks[2], 'gray', 'gray key 2');
  assertEq(marks[3], 'gray', 'black map, gray key => gray value');

  // Test edges from map&delegate => key and map&key => value.
  //
  // In general, when a&b => x, then if both a and b are black, then x must be
  // black. If either is gray and the other is marked (gray or black), then x
  // must be gray (unless otherwise reachable from black.) If neither a nor b is
  // marked at all, then they will not keep x alive.

  clearMarkObservers();

  // Black map, gray delegate => gray key

  // wm is in a variable, so is black.
  wm = new WeakMap();

  let key = Object.create(null);
  // delegate unwraps key in the 'global' compartment
  global.grayRoot().delegate = key;

  // Create a value and map to it from a gray key, then make the value a gray
  // root.
  let value = Object.create(null);
  wm.set(key, value);
  grayRoot().value = value;

  // We are interested in the mark bits of the map, key, and value, as well as
  // the mark bits of the wrapped versions in the other compartment. Note that
  // the other-compartment key is the known as the key's delegate with respect to
  // the weakmap.
  global.addMarkObservers([wm, key, value]);
  addMarkObservers([wm, key, value]);

  // Key is otherwise dead in main compartment.
  key = null;
  // Don't want value to be marked black.
  value = null;

  gc(); // Update mark bits.
  let [
    other_map_mark, other_key_mark, other_value_mark,
    map_mark, key_mark, value_mark
  ] = getMarks();
  assertEq(other_map_mark, 'dead', 'nothing points to wm in other compartment');
  assertEq(other_key_mark, 'gray', 'delegate should be gray');
  assertEq(other_value_mark, 'dead', 'nothing points to value wrapper in other compartment');
  assertEq(map_mark, 'black', 'map in var => black');
  assertEq(key_mark, 'gray', 'black map, gray delegate => gray key');
  assertEq(value_mark, 'gray', 'black map, gray delegate/key => gray value');

  // Black map, black delegate => black key

  // Blacken the delegate by pointing to it from the other global.
  global.delegate = global.grayRoot().delegate;

  gc();
  [
    other_map_mark, other_key_mark, other_value_mark,
    map_mark, key_mark, value_mark
  ] = getMarks();
  assertEq(other_map_mark, 'dead', 'nothing points to wm in other compartment');
  assertEq(other_key_mark, 'black', 'delegate held in global.delegate');
  assertEq(other_value_mark, 'dead', 'nothing points to value wrapper in other compartment');
  assertEq(map_mark, 'black', 'map in var => black');
  assertEq(key_mark, 'black', 'black map, black delegate => black key');
  assertEq(value_mark, 'black', 'black map, black key => black value');

  // Gray map, black delegate => gray key. Unfortunately, there's no way to test
  // this, because a WeakMap key's delegate is its wrapper, and there is a strong
  // edge from wrappers to wrappees. The jsapi-test in testGCGrayMarking, inside
  // TestWeakMaps, *does* test this case.

  grayRoot().map = wm;
  wm = null;

  gc();
  [
    other_map_mark, other_key_mark, other_value_mark,
    map_mark, key_mark, value_mark
  ] = getMarks();
  assertEq(other_map_mark, 'dead', 'nothing points to wm in other compartment');
  assertEq(other_key_mark, 'black', 'delegate held in global.delegate');
  assertEq(other_value_mark, 'dead', 'nothing points to value wrapper in other compartment');
  assertEq(map_mark, 'gray', 'map is a gray root');
  assertEq(key_mark, 'black', 'black delegate marks its key black');
  assertEq(value_mark, 'gray', 'gray map, black key => gray value');
}
test();

if (typeof reportCompare === "function")
  reportCompare(true, true);
