tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_xrayToExplicitResourceManagement.html (15776B)


      1 <!DOCTYPE html>
      2 <html lang="en">
      3 <!--
      4 https://bugzilla.mozilla.org/show_bug.cgi?id=1929055
      5 -->
      6 
      7 <head>
      8  <meta charset="UTF-8">
      9  <title>Test for Bug 1929055</title>
     10  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
     11  <link rel="stylesheet" type="text/css" href="chrome://global/skin" />
     12  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
     13  <script>
     14    var { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
     15      "resource://gre/modules/AppConstants.sys.mjs"
     16    );
     17    const isExplicitResourceManagementEnabled = AppConstants.ENABLE_EXPLICIT_RESOURCE_MANAGEMENT;
     18 
     19    async function go() {
     20      SimpleTest.waitForExplicitFinish();
     21 
     22      let simpleConstructors = [
     23        'DisposableStack',
     24        'AsyncDisposableStack',
     25      ];
     26 
     27      const errorObjectClasses = [
     28        'SuppressedError',
     29      ]
     30 
     31      simpleConstructors = simpleConstructors.concat(errorObjectClasses);
     32 
     33      const iwin = document.getElementById('ifr').contentWindow;
     34 
     35      if (!isExplicitResourceManagementEnabled) {
     36        for (let c of simpleConstructors) {
     37          is(iwin[c], undefined, "Constructors should not be exposed: " + c);
     38        }
     39        SimpleTest.finish();
     40        return;
     41      }
     42 
     43      await SpecialPowers.pushPrefEnv({
     44        set: [["javascript.options.experimental.explicit_resource_management", true]],
     45      });
     46 
     47      let global = Cu.getGlobalForObject.bind(Cu);
     48 
     49      // Copied from js/xpconnect/tests/chrome/test_xrayToJS.xhtml
     50      // ==== BEGIN ===
     51 
     52      // Test constructors that can be instantiated with zero arguments, or with
     53      // a fixed set of arguments provided using `...rest`.
     54      for (let c of simpleConstructors) {
     55        var args = [];
     56        if (typeof c === 'object') {
     57          args = c.args;
     58          c = c.name;
     59        }
     60        ok(iwin[c], "Constructors appear: " + c);
     61        is(iwin[c], Cu.unwaiveXrays(iwin.wrappedJSObject[c]),
     62          "we end up with the appropriate constructor: " + c);
     63        is(Cu.unwaiveXrays(Cu.waiveXrays(new iwin[c](...args)).constructor), iwin[c],
     64          "constructor property is set up right: " + c);
     65        let expectedProto = Cu.isOpaqueWrapper(new iwin[c](...args)) ?
     66          iwin.Object.prototype : iwin[c].prototype;
     67        is(Object.getPrototypeOf(new iwin[c](...args)), expectedProto,
     68          "prototype is correct: " + c);
     69        is(global(new iwin[c](...args)), iwin, "Got the right global: " + c);
     70      }
     71 
     72      var gPrototypeProperties = {};
     73      var gConstructorProperties = {};
     74      // Properties which cannot be invoked if callable without potentially
     75      // rendering the object useless.
     76      var gStatefulProperties = {};
     77 
     78      function testProtoCallables(protoCallables, xray, xrayProto, localProto, callablesExcluded) {
     79        // Handle undefined callablesExcluded.
     80        let dontCall = callablesExcluded ?? [];
     81        for (let name of protoCallables) {
     82          info("Running tests for property: " + name);
     83          // Test both methods and getter properties.
     84          function lookupCallable(obj) {
     85            let desc = null;
     86            do {
     87              desc = Object.getOwnPropertyDescriptor(obj, name);
     88              if (desc) {
     89                break;
     90              }
     91              obj = Object.getPrototypeOf(obj);
     92            } while (obj);
     93            return desc ? (desc.get || desc.value) : undefined;
     94          };
     95          ok(xrayProto.hasOwnProperty(name), `proto should have the property '${name}' as own`);
     96          ok(!xray.hasOwnProperty(name), `instance should not have the property '${name}' as own`);
     97          let method = lookupCallable(xrayProto);
     98          is(typeof method, 'function', "Methods from Xrays are functions");
     99          is(global(method), window, "Methods from Xrays are local");
    100          ok(method instanceof Function, "instanceof works on methods from Xrays");
    101          is(lookupCallable(xrayProto), method, "Holder caching works properly");
    102          is(lookupCallable(xray), method, "Proto props resolve on the instance");
    103          let local = lookupCallable(localProto);
    104          is(method.length, local.length, "Function.length identical");
    105          if (!method.length && !dontCall.includes(name)) {
    106            is(method.call(xray) + "", local.call(xray) + "",
    107              "Xray and local method results stringify identically");
    108 
    109            // If invoking this method returns something non-Xrayable (opaque), the
    110            // stringification is going to return [object Object].
    111            // This happens for set[@@iterator] and other Iterator objects.
    112            let callable = lookupCallable(xray.wrappedJSObject);
    113            if (!Cu.isOpaqueWrapper(method.call(xray)) && callable) {
    114              is(method.call(xray) + "",
    115                callable.call(xray.wrappedJSObject) + "",
    116                "Xray and waived method results stringify identically");
    117            }
    118          }
    119        }
    120      }
    121 
    122      function testCtorCallables(ctorCallables, xrayCtor, localCtor) {
    123        for (let name of ctorCallables) {
    124          // Don't try to test Function.prototype, since that is in fact a callable
    125          // but doesn't really do the things we expect callables to do here
    126          // (e.g. it's in the wrong global, since it gets Xrayed itself).
    127          if (name == "prototype" && localCtor.name == "Function") {
    128            continue;
    129          }
    130          info(`Running tests for property: ${localCtor.name}.${name}`);
    131          // Test both methods and getter properties.
    132          function lookupCallable(obj) {
    133            let desc = null;
    134            do {
    135              desc = Object.getOwnPropertyDescriptor(obj, name);
    136              obj = Object.getPrototypeOf(obj);
    137            } while (!desc);
    138            return desc.get || desc.value;
    139          };
    140 
    141          ok(xrayCtor.hasOwnProperty(name), "ctor should have the property as own");
    142          let method = lookupCallable(xrayCtor);
    143          is(typeof method, 'function', "Methods from ctor Xrays are functions");
    144          is(global(method), window, "Methods from ctor Xrays are local");
    145          ok(method instanceof Function,
    146            "instanceof works on methods from ctor Xrays");
    147          is(lookupCallable(xrayCtor), method,
    148            "Holder caching works properly on ctors");
    149          let local = lookupCallable(localCtor);
    150          is(method.length, local.length,
    151            "Function.length identical for method from ctor");
    152          // Don't try to do the return-value check on Date.now(), since there is
    153          // absolutely no reason it should return the same value each time.
    154          //
    155          // Also don't try to do the return-value check on Regexp.lastMatch and
    156          // Regexp["$&"] (which are aliases), because they get state off the global
    157          // they live in, as far as I can tell, so testing them over Xrays will be
    158          // wrong: on the Xray they will actaully get the lastMatch of _our_
    159          // global, not the Xrayed one.
    160          if (!method.length &&
    161            !(localCtor.name == "Date" && name == "now") &&
    162            !(localCtor.name == "RegExp" && (name == "lastMatch" || name == "$&"))) {
    163            is(method.call(xrayCtor) + "", local.call(xrayCtor) + "",
    164              "Xray and local method results stringify identically on constructors");
    165            is(method.call(xrayCtor) + "",
    166              lookupCallable(xrayCtor.wrappedJSObject).call(xrayCtor.wrappedJSObject) + "",
    167              "Xray and waived method results stringify identically");
    168          }
    169        }
    170      }
    171 
    172      function filterOut(array, props) {
    173        return array.filter(p => !props.includes(p));
    174      }
    175 
    176      function propertyIsGetter(obj, name) {
    177        return !!Object.getOwnPropertyDescriptor(obj, name).get;
    178      }
    179 
    180      function constructorProps(arr) {
    181        // Some props live on all constructors
    182        return arr.concat(["prototype", "length", "name"]);
    183      }
    184 
    185      // Sort an array that may contain symbols as well as strings.
    186      function sortProperties(arr) {
    187        function sortKey(prop) {
    188          return typeof prop + ":" + prop.toString();
    189        }
    190        arr.sort((a, b) => sortKey(a) < sortKey(b) ? -1 : +1);
    191      }
    192 
    193      function testXray(classname, xray, xray2, propsToSkip, ctorPropsToSkip = []) {
    194        propsToSkip = propsToSkip || [];
    195        let xrayProto = Object.getPrototypeOf(xray);
    196        let localProto = window[classname].prototype;
    197        let desiredProtoProps = Object.getOwnPropertyNames(localProto).sort();
    198 
    199        is(desiredProtoProps.toSource(),
    200          gPrototypeProperties[classname].filter(id => typeof id === "string").toSource(),
    201          "A property on the " + classname +
    202          " prototype has changed! You need a security audit from an XPConnect peer");
    203        is(Object.getOwnPropertySymbols(localProto).map(uneval).sort().toSource(),
    204          gPrototypeProperties[classname].filter(id => typeof id !== "string").map(uneval).sort().toSource(),
    205          "A symbol-keyed property on the " + classname +
    206          " prototype has been changed! You need a security audit from an XPConnect peer");
    207 
    208        let protoProps = filterOut(desiredProtoProps, propsToSkip);
    209        let protoCallables = protoProps.filter(name => propertyIsGetter(localProto, name, classname) ||
    210          typeof localProto[name] == 'function' &&
    211          name != 'constructor');
    212        let callablesExcluded = gStatefulProperties[classname];
    213        ok(!!protoCallables.length, "Need something to test");
    214        is(xrayProto, iwin[classname].prototype, "Xray proto is correct");
    215        is(xrayProto, xray.__proto__, "Proto accessors agree");
    216        var protoProto = classname == "Object" ? null : iwin.Object.prototype;
    217        is(Object.getPrototypeOf(xrayProto), protoProto, "proto proto is correct");
    218        testProtoCallables(protoCallables, xray, xrayProto, localProto, callablesExcluded);
    219        is(Object.getOwnPropertyNames(xrayProto).sort().toSource(),
    220          protoProps.toSource(), "getOwnPropertyNames works");
    221        is(Object.getOwnPropertySymbols(xrayProto).map(uneval).sort().toSource(),
    222          gPrototypeProperties[classname].filter(id => typeof id !== "string" && !propsToSkip.includes(id))
    223            .map(uneval).sort().toSource(),
    224          "getOwnPropertySymbols works");
    225 
    226        is(xrayProto.constructor, iwin[classname], "constructor property works");
    227 
    228        xrayProto.expando = 42;
    229        is(xray.expando, 42, "Xrayed instances see proto expandos");
    230        is(xray2.expando, 42, "Xrayed instances see proto expandos");
    231 
    232        // Now test constructors
    233        let localCtor = window[classname];
    234        let xrayCtor = xrayProto.constructor;
    235        // We already checked that this is the same as iwin[classname]
    236 
    237        let desiredCtorProps =
    238          Object.getOwnPropertyNames(localCtor).sort();
    239        is(desiredCtorProps.toSource(),
    240          gConstructorProperties[classname].filter(id => typeof id === "string").toSource(),
    241          "A property on the " + classname +
    242          " constructor has changed! You need a security audit from an XPConnect peer");
    243        let desiredCtorSymbols =
    244          Object.getOwnPropertySymbols(localCtor).map(uneval).sort()
    245        is(desiredCtorSymbols.toSource(),
    246          gConstructorProperties[classname].filter(id => typeof id !== "string").map(uneval).sort().toSource(),
    247          "A symbol-keyed property on the " + classname +
    248          " constructor has been changed! You need a security audit from an XPConnect peer");
    249 
    250        let ctorProps = filterOut(desiredCtorProps, ctorPropsToSkip);
    251        let ctorSymbols = filterOut(desiredCtorSymbols, ctorPropsToSkip.map(uneval));
    252        let ctorCallables = ctorProps.filter(name => propertyIsGetter(localCtor, name, classname) ||
    253          typeof localCtor[name] == 'function');
    254        testCtorCallables(ctorCallables, xrayCtor, localCtor);
    255        is(Object.getOwnPropertyNames(xrayCtor).sort().toSource(),
    256          ctorProps.toSource(), "getOwnPropertyNames works on Xrayed ctors");
    257        is(Object.getOwnPropertySymbols(xrayCtor).map(uneval).sort().toSource(),
    258          ctorSymbols.toSource(), "getOwnPropertySymbols works on Xrayed ctors");
    259      }
    260 
    261      // ==== END ===
    262 
    263      gPrototypeProperties.DisposableStack = [
    264        "adopt", "constructor", "defer", "dispose", "disposed", "move", "use",
    265        Symbol.toStringTag, Symbol.dispose
    266      ];
    267      gStatefulProperties.DisposableStack = ["dispose", Symbol.dispose, "move"];
    268      gConstructorProperties.DisposableStack = constructorProps([]);
    269 
    270      gPrototypeProperties.AsyncDisposableStack = [
    271        "adopt", "constructor", "defer", "disposeAsync", "disposed", "move", "use",
    272        Symbol.toStringTag, Symbol.asyncDispose
    273      ];
    274      gStatefulProperties.AsyncDisposableStack = ["disposeAsync", Symbol.asyncDispose, "move"];
    275      gConstructorProperties.AsyncDisposableStack = constructorProps([]);
    276 
    277      // Sort all the lists so we don't need to mutate them later (or copy them
    278      // again to sort them).
    279      for (let c of Object.keys(gPrototypeProperties))
    280        sortProperties(gPrototypeProperties[c]);
    281      for (let c of Object.keys(gConstructorProperties))
    282        sortProperties(gConstructorProperties[c]);
    283 
    284      function testDisposableStack() {
    285        testXray("DisposableStack", new iwin.DisposableStack(), new iwin.DisposableStack());
    286      }
    287 
    288      function testAsyncDisposableStack() {
    289        testXray("AsyncDisposableStack", new iwin.AsyncDisposableStack(), new iwin.AsyncDisposableStack());
    290      }
    291 
    292      function testSuppressedError() {
    293        const c = errorObjectClasses[0];
    294        const args = ['error', 'suppressed', 'some message'];
    295        var e = new iwin.SuppressedError(...args);
    296 
    297        // Copied from js/xpconnect/tests/chrome/test_xrayToJS.xhtml
    298        // ==== BEGIN ===
    299 
    300        is(Object.getPrototypeOf(e).name, c, "Prototype has correct name");
    301        is(Object.getPrototypeOf(Object.getPrototypeOf(e)), iwin.Error.prototype, "Dependent prototype set up correctly");
    302        is(e.name, c, "Exception name inherited correctly");
    303 
    304        function testProperty(name, criterion, goodReplacement, faultyReplacement) {
    305          ok(criterion(e[name]), name + " property is correct: " + e[name]);
    306          e.wrappedJSObject[name] = goodReplacement;
    307          is(e[name], goodReplacement, name + " property ok after replacement: " + goodReplacement);
    308          e.wrappedJSObject[name] = faultyReplacement;
    309          is(e[name], name == 'message' ? "" : undefined, name + " property skipped after suspicious replacement");
    310        }
    311        testProperty('message', x => x == 'some message', 'some other message', 42);
    312        testProperty('fileName', x => x == '', 'otherFilename.html', new iwin.Object());
    313        testProperty('columnNumber', x => x == 1, 99, 99.5);
    314        testProperty('lineNumber', x => x == 0, 50, 'foo');
    315 
    316        // ==== END ===
    317 
    318        e.wrappedJSObject.error = 42;
    319        is(e.wrappedJSObject.error, 42, "errors is a plain data property");
    320        is(e.error, 42, "error visible over Xrays");
    321 
    322        e.wrappedJSObject.suppressed = 43;
    323        is(e.wrappedJSObject.suppressed, 43, "suppressed is a plain data property");
    324        is(e.suppressed, 43, "suppressed visible over Xrays");
    325      }
    326 
    327      testDisposableStack();
    328 
    329      testAsyncDisposableStack();
    330 
    331      testSuppressedError();
    332 
    333      await SpecialPowers.popPrefEnv();
    334      SimpleTest.finish();
    335    }
    336  </script>
    337 </head>
    338 
    339 <body>
    340  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1929055">Mozilla Bug 1929055</a>
    341 
    342  <iframe id="ifr" onload="go();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html" />
    343 </body>
    344 
    345 </html>