/*
 * Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/licenses/publicdomain/
 */

var gTestfile = "getPrototypeOf.js";
var BUGNUMBER = 888969;
var summary = "Scripted proxies' [[GetPrototypeOf]] behavior";

print(BUGNUMBER + ": " + summary);

/**************
 * BEGIN TEST *
 **************/

const log = [];

function observe(obj)
{
  var observingHandler = new Proxy({}, {
    get(target, p, receiver) {
      log.push(p);
      return Reflect.get(target, p, receiver);
    }
  });

  return new Proxy(obj, observingHandler);
}

function nop() {}

var p, h;

// 1. Let handler be the value of the [[ProxyHandler]] internal slot of O.
// 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object.
var rev = Proxy.revocable({}, {});
p = rev.proxy;

assertEq(Object.getPrototypeOf(p), Object.prototype);
rev.revoke();
assertThrowsInstanceOf(() => Object.getPrototypeOf(p), TypeError);

// 4. Let target be the value of the [[ProxyTarget]] internal slot of O.
// 5. Let trap be ? GetMethod(handler, "getPrototypeOf").

// Getting handler.getPrototypeOf might throw.
assertThrowsValue(() => Object.getPrototypeOf(new Proxy({},
                                                        { get getPrototypeOf() {
                                                            throw 17;
                                                          } })),
                  17);

// The handler's getPrototypeOf, once gotten, might throw.
p = new Proxy({}, { getPrototypeOf() { throw 42; } });

assertThrowsValue(() => Object.getPrototypeOf(p), 42);

// The trap might not be callable.
p = new Proxy({}, { getPrototypeOf: 17 });

assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
                       TypeError);

// 6. If trap is undefined, then
//    a. Return ? target.[[GetPrototypeOf]]().

var x, tp;

tp =
  new Proxy(new Number(8675309), // behavior overridden by getPrototypeOf
            { getPrototypeOf() { x = "getPrototypeOf trap"; return null; } });

// The target's [[GetPrototypeOf]] should be invoked if the handler's
// .getPrototypeOf is undefined.
p = new Proxy(tp, { getPrototypeOf: undefined });
x = "unset";
assertEq(Object.getPrototypeOf(p), null);
assertEq(x, "getPrototypeOf trap");

// Likewise if the handler's .getPrototypeOf is null.
p = new Proxy(tp, { getPrototypeOf: null });
x = "unset";
assertEq(Object.getPrototypeOf(p), null);
assertEq(x, "getPrototypeOf trap");

// Now the target is an empty object with a Number object as its [[Prototype]].
var customProto = new Number(8675309);
tp =
  new Proxy({},
            { getPrototypeOf() {
                x = "getPrototypeOf trap";
                return customProto;
              } });

// The target's [[GetPrototypeOf]] should be invoked if the handler's
// .getPrototypeOf is undefined.
p = new Proxy(tp, { getPrototypeOf: undefined });
x = "unset";
assertEq(Object.getPrototypeOf(p), customProto);
assertEq(x, "getPrototypeOf trap");

// Likewise if the handler's .getPrototypeOf is null.
p = new Proxy(tp, { getPrototypeOf: null });
x = "unset";
assertEq(Object.getPrototypeOf(p), customProto);
assertEq(x, "getPrototypeOf trap");

// 7. Let handlerProto be ? Call(trap, handler, « target »).

// The trap callable might throw.
p = new Proxy({}, { getPrototypeOf() { throw "ohai"; } });

assertThrowsValue(() => Object.getPrototypeOf(p),
                  "ohai");

var throwingTrap =
  new Proxy(function() { throw "not called"; },
            { apply() { throw 37; } });

p = new Proxy({}, { getPrototypeOf: throwingTrap });

assertThrowsValue(() => Object.getPrototypeOf(p),
                  37);

// The trap callable must *only* be called.
p = new Proxy({},
              {
                getPrototypeOf: observe(function() { throw "boo-urns"; })
              });

log.length = 0;
assertThrowsValue(() => Object.getPrototypeOf(p),
                  "boo-urns");

assertEq(log.length, 1);
assertEq(log[0], "apply");

// 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception.

var rval;

var typeTestingTarget = {};
p = new Proxy(typeTestingTarget, { getPrototypeOf() { return rval; } });

function returnsPrimitives()
{
  rval = undefined;
  assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
                         TypeError);

  rval = true;
  assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
                         TypeError);

  rval = false;
  assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
                         TypeError);

  rval = 0.0;
  assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
                         TypeError);

  rval = -0.0;
  assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
                         TypeError);

  rval = 3.141592654;
  assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
                         TypeError);

  rval = NaN;
  assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
                         TypeError);

  rval = -Infinity;
  assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
                         TypeError);

  rval = "[[Prototype]] FOR REALZ";
  assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
                         TypeError);

  rval = Symbol("[[Prototype]] FOR REALZ");
  assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
                         TypeError);
}

returnsPrimitives();
Object.preventExtensions(typeTestingTarget);
returnsPrimitives();

// 9. Let extensibleTarget be ? IsExtensible(target).

var act, extens;

var typeTestingProxyTarget =
  new Proxy({}, { isExtensible() {
                    seen = act();
                    return extens;
                  } });

p = new Proxy(typeTestingProxyTarget, { getPrototypeOf() { return rval; } });

rval = null;
act = () => { throw "fnord" };
assertThrowsValue(() => Object.getPrototypeOf(p),
                  "fnord");

rval = /abc/;
act = () => { throw "fnord again" };
assertThrowsValue(() => Object.getPrototypeOf(p),
                  "fnord again");

rval = Object.prototype;
act = () => { throw "fnord" };
assertThrowsValue(() => Object.getPrototypeOf(p),
                  "fnord");

// 10. If extensibleTarget is true, return handlerProto.

p = new Proxy({}, { getPrototypeOf() { return rval; } });

rval = Number.prototype;
assertEq(Object.getPrototypeOf(p), Number.prototype);

// 11. Let targetProto be ? target.[[GetPrototypeOf]]().

var targetProto;

var targetWithProto =
  new Proxy(Object.preventExtensions(Object.create(null)),
            { getPrototypeOf() { act2(); return targetProto; } });

p = new Proxy(targetWithProto,
              { getPrototypeOf() { act1(); return rval; } });

rval = null;
targetProto = null;

var regex = /targetProto/;

act1 = () => log.push("act1");
act2 = () => log.push("act2");

log.length = 0;
assertEq(Object.getPrototypeOf(p), null);
assertEq(log.length, 2);
assertEq(log[0], "act1");
assertEq(log[1], "act2");

act1 = () => log.push("act1 again");
act2 = () => { throw "target throw"; };

log.length = 0;
assertThrowsValue(() => Object.getPrototypeOf(p),
                  "target throw");
assertEq(log.length, 1);
assertEq(log[0], "act1 again");

// 12. If SameValue(handlerProto, targetProto) is false, throw a TypeError exception.

act1 = act2 = nop;
rval = /a/;
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
                       TypeError);

// 13. Return handlerProto.

rval = null;
targetProto = null;

assertEq(Object.getPrototypeOf(p), null);

p = new Proxy(Object.preventExtensions(new Number(55)),
              { getPrototypeOf() { return Number.prototype; } });

assertEq(Object.getPrototypeOf(p), Number.prototype);

/******************************************************************************/

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

print("Tests complete");
