tor-browser

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

unwrapping.js (8984B)


      1 // |reftest| skip-if(!this.hasOwnProperty("Intl"))
      2 
      3 // Test UnwrapNumberFormat operation.
      4 
      5 const numberFormatFunctions = [];
      6 numberFormatFunctions.push({
      7    function: Intl.NumberFormat.prototype.resolvedOptions,
      8    unwrap: true,
      9 });
     10 numberFormatFunctions.push({
     11    function: Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format").get,
     12    unwrap: true,
     13 });
     14 numberFormatFunctions.push({
     15    function: Intl.NumberFormat.prototype.formatToParts,
     16    unwrap: false,
     17 });
     18 
     19 function IsIntlService(c) {
     20    return typeof c === "function" &&
     21           c.hasOwnProperty("prototype") &&
     22           c.prototype.hasOwnProperty("resolvedOptions");
     23 }
     24 
     25 function IsObject(o) {
     26    return Object(o) === o;
     27 }
     28 
     29 function IsPrimitive(o) {
     30    return Object(o) !== o;
     31 }
     32 
     33 function intlObjects(ctor) {
     34    let args = [];
     35    if (ctor === Intl.DisplayNames) {
     36        // Intl.DisplayNames can't be constructed without any arguments.
     37        args = [undefined, {type: "language"}];
     38    }
     39 
     40    return [
     41        // Instance of an Intl constructor.
     42        new ctor(...args),
     43 
     44        // Instance of a subclassed Intl constructor.
     45        new class extends ctor {}(...args),
     46 
     47        // Intl object not inheriting from its default prototype.
     48        Object.setPrototypeOf(new ctor(...args), Object.prototype),
     49    ];
     50 }
     51 
     52 function thisValues(C) {
     53    const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsIntlService);
     54 
     55    return [
     56        // Primitive values.
     57        ...[undefined, null, true, "abc", Symbol(), 123],
     58 
     59        // Object values.
     60        ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
     61 
     62        // Intl objects.
     63        ...[].concat(...intlConstructors.filter(ctor => ctor !== C).map(intlObjects)),
     64 
     65        // Object inheriting from an Intl constructor prototype.
     66        ...intlConstructors.map(ctor => Object.create(ctor.prototype)),
     67    ];
     68 }
     69 
     70 const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0];
     71 
     72 // Test Intl.NumberFormat.prototype methods.
     73 for (let {function: numberFormatFunction, unwrap} of numberFormatFunctions) {
     74    // Test a TypeError is thrown when the this-value isn't an initialized
     75    // Intl.NumberFormat instance.
     76    for (let thisValue of thisValues(Intl.NumberFormat)) {
     77        assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
     78    }
     79 
     80    // And test no error is thrown for initialized Intl.NumberFormat instances.
     81    for (let thisValue of intlObjects(Intl.NumberFormat)) {
     82        numberFormatFunction.call(thisValue);
     83    }
     84 
     85    // Manually add [[FallbackSymbol]] to objects and then repeat the tests from above.
     86    for (let thisValue of thisValues(Intl.NumberFormat)) {
     87        assertThrowsInstanceOf(() => numberFormatFunction.call({
     88            __proto__: Intl.NumberFormat.prototype,
     89            [intlFallbackSymbol]: thisValue,
     90        }), TypeError);
     91    }
     92 
     93    for (let thisValue of intlObjects(Intl.NumberFormat)) {
     94        let obj = {
     95            __proto__: Intl.NumberFormat.prototype,
     96            [intlFallbackSymbol]: thisValue,
     97        };
     98        if (unwrap) {
     99            numberFormatFunction.call(obj);
    100        } else {
    101            assertThrowsInstanceOf(() => numberFormatFunction.call(obj), TypeError);
    102        }
    103    }
    104 
    105    // Ensure [[FallbackSymbol]] isn't retrieved for Intl.NumberFormat instances.
    106    for (let thisValue of intlObjects(Intl.NumberFormat)) {
    107        Object.defineProperty(thisValue, intlFallbackSymbol, {
    108            get() { assertEq(false, true); }
    109        });
    110        numberFormatFunction.call(thisValue);
    111    }
    112 
    113    // Ensure [[FallbackSymbol]] is only retrieved for objects inheriting from Intl.NumberFormat.prototype.
    114    for (let thisValue of thisValues(Intl.NumberFormat).filter(IsObject)) {
    115        if (Intl.NumberFormat.prototype.isPrototypeOf(thisValue))
    116            continue;
    117        Object.defineProperty(thisValue, intlFallbackSymbol, {
    118            get() { assertEq(false, true); }
    119        });
    120        assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
    121    }
    122 
    123    // Repeat the test from above, but also change Intl.NumberFormat[@@hasInstance]
    124    // so it always returns |true|.
    125    for (let thisValue of thisValues(Intl.NumberFormat).filter(IsObject)) {
    126        let isPrototypeOf = Intl.NumberFormat.prototype.isPrototypeOf(thisValue);
    127        let hasInstanceCalled = false, symbolGetterCalled = false;
    128        Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
    129            value() {
    130                assertEq(hasInstanceCalled, false);
    131                hasInstanceCalled = true;
    132                return true;
    133            }, configurable: true
    134        });
    135        Object.defineProperty(thisValue, intlFallbackSymbol, {
    136            get() {
    137                assertEq(symbolGetterCalled, false);
    138                symbolGetterCalled = true;
    139                return null;
    140            }, configurable: true
    141        });
    142 
    143        assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
    144 
    145        delete Intl.NumberFormat[Symbol.hasInstance];
    146 
    147        assertEq(hasInstanceCalled, false);
    148        assertEq(symbolGetterCalled, unwrap && isPrototypeOf);
    149    }
    150 
    151    // Test with primitive values.
    152    for (let thisValue of thisValues(Intl.NumberFormat).filter(IsPrimitive)) {
    153        // Ensure @@hasInstance is not called.
    154        Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
    155            value() { assertEq(true, false); }, configurable: true
    156        });
    157        let isUndefinedOrNull = thisValue === undefined || thisValue === null;
    158        let symbolHolder;
    159        if (!isUndefinedOrNull) {
    160            // Ensure the fallback symbol isn't retrieved from the primitive wrapper prototype.
    161            symbolHolder = Object.getPrototypeOf(thisValue);
    162            Object.defineProperty(symbolHolder, intlFallbackSymbol, {
    163                get() { assertEq(true, false); }, configurable: true
    164            });
    165        }
    166 
    167        assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
    168 
    169        delete Intl.NumberFormat[Symbol.hasInstance];
    170        if (!isUndefinedOrNull)
    171            delete symbolHolder[intlFallbackSymbol];
    172    }
    173 }
    174 
    175 // Test format() returns the correct result for objects initialized as Intl.NumberFormat instances.
    176 {
    177    // An actual Intl.NumberFormat instance.
    178    let numberFormat = new Intl.NumberFormat();
    179 
    180    // An object initialized as a NumberFormat instance.
    181    let thisValue = Object.create(Intl.NumberFormat.prototype);
    182    Intl.NumberFormat.call(thisValue);
    183 
    184    // Object with [[FallbackSymbol]] set to NumberFormat instance.
    185    let fakeObj = {
    186        __proto__: Intl.NumberFormat.prototype,
    187        [intlFallbackSymbol]: numberFormat,
    188    };
    189 
    190    for (let number of [0, 1, 1.5, Infinity, NaN]) {
    191        let expected = numberFormat.format(number);
    192        assertEq(thisValue.format(number), expected);
    193        assertEq(thisValue[intlFallbackSymbol].format(number), expected);
    194        assertEq(fakeObj.format(number), expected);
    195    }
    196 }
    197 
    198 // Ensure formatToParts() doesn't use the fallback semantics.
    199 {
    200    let formatToParts = Intl.NumberFormat.prototype.formatToParts;
    201 
    202    // An object initialized as a NumberFormat instance.
    203    let thisValue = Object.create(Intl.NumberFormat.prototype);
    204    Intl.NumberFormat.call(thisValue);
    205    assertThrowsInstanceOf(() => formatToParts.call(thisValue), TypeError);
    206 
    207    // Object with [[FallbackSymbol]] set to NumberFormat instance.
    208    let fakeObj = {
    209        __proto__: Intl.NumberFormat.prototype,
    210        [intlFallbackSymbol]: new Intl.NumberFormat(),
    211    };
    212    assertThrowsInstanceOf(() => formatToParts.call(fakeObj), TypeError);
    213 }
    214 
    215 // Test resolvedOptions() returns the same results.
    216 {
    217    // An actual Intl.NumberFormat instance.
    218    let numberFormat = new Intl.NumberFormat();
    219 
    220    // An object initialized as a NumberFormat instance.
    221    let thisValue = Object.create(Intl.NumberFormat.prototype);
    222    Intl.NumberFormat.call(thisValue);
    223 
    224    // Object with [[FallbackSymbol]] set to NumberFormat instance.
    225    let fakeObj = {
    226        __proto__: Intl.NumberFormat.prototype,
    227        [intlFallbackSymbol]: numberFormat,
    228    };
    229 
    230    function assertEqOptions(actual, expected) {
    231        actual = Object.entries(actual);
    232        expected = Object.entries(expected);
    233 
    234        assertEq(actual.length, expected.length, "options count mismatch");
    235        for (var i = 0; i < expected.length; i++) {
    236            assertEq(actual[i][0], expected[i][0], "key mismatch at " + i);
    237            assertEq(actual[i][1], expected[i][1], "value mismatch at " + i);
    238        }
    239    }
    240 
    241    let expected = numberFormat.resolvedOptions();
    242    assertEqOptions(thisValue.resolvedOptions(), expected);
    243    assertEqOptions(thisValue[intlFallbackSymbol].resolvedOptions(), expected);
    244    assertEqOptions(fakeObj.resolvedOptions(), expected);
    245 }
    246 
    247 if (typeof reportCompare === "function")
    248    reportCompare(true, true);