content/xbl/test/file_bug821850.xhtml
author Bobby Holley <bobbyholley@gmail.com>
Thu, 09 May 2013 09:16:02 -0700
changeset 131398 d8d4867f71c1725b193f9d6d20e5eea084d6459e
parent 127524 984df1da7b15442e61a064b7e10798720ec5c352
child 131926 049889b25a7967662bf2c57978bcf3b54f50a663
permissions -rw-r--r--
Bug 857356 - Fix in-content XBL tests. r=bz

<html xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=821850
-->
<head>
  <bindings xmlns="http://www.mozilla.org/xbl">
    <binding id="testBinding">
      <implementation>
        <constructor>
          // Store a property as an expando on the bound element.
          this._prop = "propVal";

          // Wait for both constructors to fire.
          window.constructorCount = (window.constructorCount + 1) || 1;
          if (window.constructorCount != 2)
            return;

          // Grab some basic infrastructure off the content window.
          var win = XPCNativeWrapper.unwrap(window);
          SpecialPowers = win.SpecialPowers;
          Cu = SpecialPowers.Cu;
          is = win.is;
          ok = win.ok;
          SimpleTest = win.SimpleTest;

          // Stick some expandos on the content window.
          window.xrayExpando = 3;
          win.primitiveExpando = 11;
          win.stringExpando = "stringExpando";
          win.objectExpando = { foo: 12 };
          win.globalExpando = SpecialPowers.unwrap(Cu.getGlobalForObject({}));
          win.functionExpando = function() { return "called" };
          win.functionExpando.prop = 2;

          // Make sure we're Xraying.
          ok(Cu.isXrayWrapper(window), "Window is Xrayed");
          ok(Cu.isXrayWrapper(document), "Document is Xrayed");

          var bound = document.getElementById('bound');
          ok(bound, "bound is non-null");
          is(bound.method('baz'), "method:baz", "Xray methods work");
          is(bound.prop, "propVal", "Property Xrays work");
          is(bound.primitiveField, undefined, "Xrays don't show fields");
          is(bound.wrappedJSObject.primitiveField, 2, "Waiving Xrays show fields");

          // This gets invoked by an event handler.
          window.finish = function() {
            // Content messed with stuff. Make sure we still see the right thing.
            is(bound.method('bay'), "method:bay", "Xray methods work");
            is(bound.wrappedJSObject.method('bay'), "hah", "Xray waived methods work");
            is(bound.prop, "set:someOtherVal", "Xray props work");
            is(bound.wrappedJSObject.prop, "redefined", "Xray waived props work");
            is(bound.wrappedJSObject.primitiveField, 321, "Can't do anything about redefined fields");

            SimpleTest.finish();
          }

          // Hand things off to content. Content will call us back.
          win.go();
        </constructor>
        <field name="primitiveField">2</field>
        <method name="method">
          <parameter name="arg" />
          <body>
            return "method:" + arg;
          </body>
        </method>
        <method name="passMeAJSObject">
          <parameter name="arg" />
          <body>
            is(typeof arg.prop, 'undefined', "No properties");
            is(Object.getOwnPropertyNames(arg).length, 0, "Should have no own properties");
            try {
              arg.foo = 2;
              ok(true, "Stuff fails silently");
            } catch (e) {
              ok(false, "Stuff should fail silently");
            }
            is(typeof arg.foo, 'undefined', "Shouldn't place props");
          </body>
        </method>
        <property name="prop">
          <getter>return this._prop;</getter>
          <setter>this._prop = "set:" + val;</setter>
        </property>
      </implementation>
      <handlers>
        <handler event="testevent" action="ok(true, 'called event handler'); finish();"/>
      </handlers>
    </binding>
  </bindings>
  <script type="application/javascript">
  <![CDATA[

  ok = parent.ok;
  is = parent.is;
  SimpleTest = parent.SimpleTest;
  SpecialPowers = parent.SpecialPowers;

  // Test the Xray waiving behavior when accessing fields. We should be able to
  // see sequential JS-implemented properties, but should regain Xrays when we
  // hit a native property.
  window.contentVal = { foo: 10, rabbit: { hole: { bar: 100, win: window} } };
  ok(true, "Set contentVal");

  function go() {
    "use strict";

    // Test what we can and cannot access in the XBL scope.
    is(typeof window.xrayExpando, "undefined", "Xray expandos are private to the caller");
    is(window.primitiveExpando, 11, "Can see waived expandos");
    is(window.stringExpando, "stringExpando", "Can see waived expandos");
    is(typeof window.objectExpando, "object", "object expando exists");
    checkThrows(function() window.objectExpando.foo);
    is(SpecialPowers.wrap(window.objectExpando).foo, 12, "SpecialPowers sees the right thing");
    is(typeof window.globalExpando, "object", "Can see global object");
    checkThrows(function() window.globalExpando.win);
    is(window.functionExpando(), "called", "XBL functions are callable");
    checkThrows(function() window.functionExpando.prop);

    // Inspect the bound element.
    var bound = document.getElementById('bound');
    is(bound.primitiveField, 2, "Can see primitive fields");
    is(bound.method("foo"), "method:foo", "Can invoke XBL method from content");
    is(bound.prop, "propVal", "Can access properties from content");
    bound.prop = "someOtherVal";
    is(bound.prop, "set:someOtherVal", "Can set properties from content");

    // Make sure we can't pass JS objects to the XBL scope.
    var proto = bound.__proto__;
    proto.passMeAJSObject({prop: 2});

    //
    // Try sticking a bunch of stuff on the prototype object.
    //

    proto.someExpando = 201;
    is(bound.someExpando, 201, "Can stick non-XBL properties on the proto");

    // Previously, this code checked that content couldn't tamper with its XBL
    // prototype. But we decided to allow this to reduce regression risk, so for
    // now just check that this works.
    function checkMayTamper(obj, propName, desc) {
      var accessor = !('value' in Object.getOwnPropertyDescriptor(obj, propName));
      if (!accessor)
        checkAllowed(function() { obj[propName] = function() {} }, desc + ": assign");
      checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true, value: 3}) }, desc + ": define with value");
      checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true, writable: true}) }, desc + ": make writable");
      checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true}) }, desc + ": make configurable");
      checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true, get: function() {}}) }, desc + ": define with getter");
      checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true, set: function() {}}) }, desc + ": define with setter");

      // Windows are implemented as proxies, and Proxy::delete_ doesn't currently
      // pass strict around. Work around it in the window.binding case by just
      // checking if delete returns false.
      // manually.
      checkAllowed(function() { delete obj[propName]; }, desc + ": delete");

      if (!accessor)
        checkAllowed(function() { obj[propName] = function() {} }, desc + ": assign (again)");
    }

    // Make sure content can do whatever it wants with the prototype.
    checkMayTamper(proto, 'method', "XBL Proto Method");
    checkMayTamper(proto, 'prop', "XBL Proto Prop");
    checkMayTamper(proto, 'primitiveField', "XBL Field Accessor");

    // As above, check that content can do what it wants with the prototype's
    // property on the global.
    var protoName, count = 0;
    for (var k of Object.getOwnPropertyNames(window)) {
      if (!/testBinding/.exec(k))
        continue;
      count++;
      protoName = k;
    }
    is(count, 1, "Should be exactly one prototype object");
    checkMayTamper(window, protoName, "XBL prototype prop on window");

    // Tamper with the derived object. This doesn't affect the XBL scope thanks
    // to Xrays.
    bound.method = function() { return "heh"; };
    Object.defineProperty(bound, 'method', {value: function() { return "hah" }});
    Object.defineProperty(bound, 'prop', {value: "redefined"});
    bound.primitiveField = 321;

    // Hand control back to the XBL scope by dispatching an event on the bound element.
    bound.dispatchEvent(new CustomEvent('testevent'));
  }

  function checkThrows(fn) {
    try { fn(); ok(false, "Should have thrown"); }
    catch (e) { ok(!!/denied|insecure/.exec(e), "Should have thrown security exception: " + e); }
  }

  function checkAllowed(fn, desc) {
    try { fn(); ok(true, desc + ": Didn't throw"); }
    catch (e) { ok(false, desc + ": Threw: " + e); }
  }

  function setup() {
    // When the bindings are applied, the constructor will be invoked and the
    // test will continue.
    document.getElementById('bound').style.MozBinding = 'url(#testBinding)';
    document.getElementById('bound2').style.MozBinding = 'url(#testBinding)';
  }

  ]]>
</script>
</head>
<body onload="setup()">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=821850">Mozilla Bug 821850</a>
<p id="display"></p>
<div id="content">
  <div id="bound">Bound element</div>
  <div id="bound2">Bound element</div>
</div>
<pre id="test">
</pre>
</body>
</html>