tor-browser

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

Blob-constructor.any.js (13451B)


      1 // META: title=Blob constructor
      2 // META: script=../support/Blob.js
      3 'use strict';
      4 
      5 test(function() {
      6  assert_true("Blob" in globalThis, "globalThis should have a Blob property.");
      7  assert_equals(Blob.length, 0, "Blob.length should be 0.");
      8  assert_true(Blob instanceof Function, "Blob should be a function.");
      9 }, "Blob interface object");
     10 
     11 // Step 1.
     12 test(function() {
     13  var blob = new Blob();
     14  assert_true(blob instanceof Blob);
     15  assert_equals(String(blob), '[object Blob]');
     16  assert_equals(blob.size, 0);
     17  assert_equals(blob.type, "");
     18 }, "Blob constructor with no arguments");
     19 test(function() {
     20  assert_throws_js(TypeError, function() { var blob = Blob(); });
     21 }, "Blob constructor with no arguments, without 'new'");
     22 test(function() {
     23  var blob = new Blob;
     24  assert_true(blob instanceof Blob);
     25  assert_equals(blob.size, 0);
     26  assert_equals(blob.type, "");
     27 }, "Blob constructor without brackets");
     28 test(function() {
     29  var blob = new Blob(undefined);
     30  assert_true(blob instanceof Blob);
     31  assert_equals(String(blob), '[object Blob]');
     32  assert_equals(blob.size, 0);
     33  assert_equals(blob.type, "");
     34 }, "Blob constructor with undefined as first argument");
     35 
     36 // blobParts argument (WebIDL).
     37 test(function() {
     38  var args = [
     39    null,
     40    true,
     41    false,
     42    0,
     43    1,
     44    1.5,
     45    "FAIL",
     46    new Date(),
     47    new RegExp(),
     48    {},
     49    { 0: "FAIL", length: 1 },
     50  ];
     51  args.forEach(function(arg) {
     52    assert_throws_js(TypeError, function() {
     53      new Blob(arg);
     54    }, "Should throw for argument " + format_value(arg) + ".");
     55  });
     56 }, "Passing non-objects, Dates and RegExps for blobParts should throw a TypeError.");
     57 
     58 test_blob(function() {
     59  return new Blob({
     60    [Symbol.iterator]: Array.prototype[Symbol.iterator],
     61  });
     62 }, {
     63  expected: "",
     64  type: "",
     65  desc: "A plain object with @@iterator should be treated as a sequence for the blobParts argument."
     66 });
     67 test(t => {
     68  const blob = new Blob({
     69    [Symbol.iterator]() {
     70      var i = 0;
     71      return {next: () => [
     72        {done:false, value:'ab'},
     73        {done:false, value:'cde'},
     74        {done:true}
     75      ][i++]
     76      };
     77    }
     78  });
     79  assert_equals(blob.size, 5, 'Custom @@iterator should be treated as a sequence');
     80 }, "A plain object with custom @@iterator should be treated as a sequence for the blobParts argument.");
     81 test_blob(function() {
     82  return new Blob({
     83    [Symbol.iterator]: Array.prototype[Symbol.iterator],
     84    0: "PASS",
     85    length: 1
     86  });
     87 }, {
     88  expected: "PASS",
     89  type: "",
     90  desc: "A plain object with @@iterator and a length property should be treated as a sequence for the blobParts argument."
     91 });
     92 test_blob(function() {
     93  return new Blob(new String("xyz"));
     94 }, {
     95  expected: "xyz",
     96  type: "",
     97  desc: "A String object should be treated as a sequence for the blobParts argument."
     98 });
     99 test_blob(function() {
    100  return new Blob(new Uint8Array([1, 2, 3]));
    101 }, {
    102  expected: "123",
    103  type: "",
    104  desc: "A Uint8Array object should be treated as a sequence for the blobParts argument."
    105 });
    106 
    107 var test_error = {
    108  name: "test",
    109  message: "test error",
    110 };
    111 
    112 test(function() {
    113  var obj = {
    114    [Symbol.iterator]: Array.prototype[Symbol.iterator],
    115    get length() { throw test_error; }
    116  };
    117  assert_throws_exactly(test_error, function() {
    118    new Blob(obj);
    119  });
    120 }, "The length getter should be invoked and any exceptions should be propagated.");
    121 
    122 test(function() {
    123  assert_throws_exactly(test_error, function() {
    124    var obj = {
    125      [Symbol.iterator]: Array.prototype[Symbol.iterator],
    126      length: {
    127        valueOf: null,
    128        toString: function() { throw test_error; }
    129      }
    130    };
    131    new Blob(obj);
    132  });
    133  assert_throws_exactly(test_error, function() {
    134    var obj = {
    135      [Symbol.iterator]: Array.prototype[Symbol.iterator],
    136      length: { valueOf: function() { throw test_error; } }
    137    };
    138    new Blob(obj);
    139  });
    140 }, "ToUint32 should be applied to the length and any exceptions should be propagated.");
    141 
    142 test(function() {
    143  var received = [];
    144  var obj = {
    145    get [Symbol.iterator]() {
    146      received.push("Symbol.iterator");
    147      return Array.prototype[Symbol.iterator];
    148    },
    149    get length() {
    150      received.push("length getter");
    151      return {
    152        valueOf: function() {
    153          received.push("length valueOf");
    154          return 3;
    155        }
    156      };
    157    },
    158    get 0() {
    159      received.push("0 getter");
    160      return {
    161        toString: function() {
    162          received.push("0 toString");
    163          return "a";
    164        }
    165      };
    166    },
    167    get 1() {
    168      received.push("1 getter");
    169      throw test_error;
    170    },
    171    get 2() {
    172      received.push("2 getter");
    173      assert_unreached("Should not call the getter for 2 if the getter for 1 threw.");
    174    }
    175  };
    176  assert_throws_exactly(test_error, function() {
    177    new Blob(obj);
    178  });
    179  assert_array_equals(received, [
    180    "Symbol.iterator",
    181    "length getter",
    182    "length valueOf",
    183    "0 getter",
    184    "0 toString",
    185    "length getter",
    186    "length valueOf",
    187    "1 getter",
    188  ]);
    189 }, "Getters and value conversions should happen in order until an exception is thrown.");
    190 
    191 // XXX should add tests edge cases of ToLength(length)
    192 
    193 test(function() {
    194  assert_throws_exactly(test_error, function() {
    195    new Blob([{ toString: function() { throw test_error; } }]);
    196  }, "Throwing toString");
    197  assert_throws_exactly(test_error, function() {
    198    new Blob([{ toString: undefined, valueOf: function() { throw test_error; } }]);
    199  }, "Throwing valueOf");
    200  assert_throws_exactly(test_error, function() {
    201    new Blob([{
    202      toString: function() { throw test_error; },
    203      valueOf: function() { assert_unreached("Should not call valueOf if toString is present."); }
    204    }]);
    205  }, "Throwing toString and valueOf");
    206  assert_throws_js(TypeError, function() {
    207    new Blob([{toString: null, valueOf: null}]);
    208  }, "Null toString and valueOf");
    209 }, "ToString should be called on elements of the blobParts array and any exceptions should be propagated.");
    210 
    211 test_blob(function() {
    212  var arr = [
    213    { toString: function() { arr.pop(); return "PASS"; } },
    214    { toString: function() { assert_unreached("Should have removed the second element of the array rather than called toString() on it."); } }
    215  ];
    216  return new Blob(arr);
    217 }, {
    218  expected: "PASS",
    219  type: "",
    220  desc: "Changes to the blobParts array should be reflected in the returned Blob (pop)."
    221 });
    222 
    223 test_blob(function() {
    224  var arr = [
    225    {
    226      toString: function() {
    227        if (arr.length === 3) {
    228          return "A";
    229        }
    230        arr.unshift({
    231          toString: function() {
    232            assert_unreached("Should only access index 0 once.");
    233          }
    234        });
    235        return "P";
    236      }
    237    },
    238    {
    239      toString: function() {
    240        return "SS";
    241      }
    242    }
    243  ];
    244  return new Blob(arr);
    245 }, {
    246  expected: "PASS",
    247  type: "",
    248  desc: "Changes to the blobParts array should be reflected in the returned Blob (unshift)."
    249 });
    250 
    251 test_blob(function() {
    252  // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17652
    253  return new Blob([
    254    null,
    255    undefined,
    256    true,
    257    false,
    258    0,
    259    1,
    260    new String("stringobject"),
    261    [],
    262    ['x', 'y'],
    263    {},
    264    { 0: "FAIL", length: 1 },
    265    { toString: function() { return "stringA"; } },
    266    { toString: undefined, valueOf: function() { return "stringB"; } },
    267    { valueOf: function() { assert_unreached("Should not call valueOf if toString is present on the prototype."); } }
    268  ]);
    269 }, {
    270  expected: "nullundefinedtruefalse01stringobjectx,y[object Object][object Object]stringAstringB[object Object]",
    271  type: "",
    272  desc: "ToString should be called on elements of the blobParts array."
    273 });
    274 
    275 test_blob(function() {
    276  return new Blob([
    277    new ArrayBuffer(8)
    278  ]);
    279 }, {
    280  expected: "\0\0\0\0\0\0\0\0",
    281  type: "",
    282  desc: "ArrayBuffer elements of the blobParts array should be supported."
    283 });
    284 
    285 test_blob(function() {
    286  return new Blob([
    287    new Uint8Array([0x50, 0x41, 0x53, 0x53]),
    288    new Int8Array([0x50, 0x41, 0x53, 0x53]),
    289    new Uint16Array([0x4150, 0x5353]),
    290    new Int16Array([0x4150, 0x5353]),
    291    new Uint32Array([0x53534150]),
    292    new Int32Array([0x53534150]),
    293    new Float32Array([0xD341500000])
    294  ]);
    295 }, {
    296  expected: "PASSPASSPASSPASSPASSPASSPASS",
    297  type: "",
    298  desc: "Passing typed arrays as elements of the blobParts array should work."
    299 });
    300 test_blob(function() {
    301  return new Blob([
    302    new Float16Array([2.65625, 58.59375])
    303  ]);
    304 }, {
    305  expected: "PASS",
    306  type: "",
    307  desc: "Passing a Float16Array as element of the blobParts array should work."
    308 });
    309 test_blob(function() {
    310  return new Blob([
    311    // 0x535 3415053534150
    312    // 0x535 = 0b010100110101 -> Sign = +, Exponent = 1333 - 1023 = 310
    313    // 0x13415053534150 * 2**(-52)
    314    // ==> 0x13415053534150 * 2**258 = 2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680
    315    new Float64Array([2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680])
    316  ]);
    317 }, {
    318  expected: "PASSPASS",
    319  type: "",
    320  desc: "Passing a Float64Array as element of the blobParts array should work."
    321 });
    322 
    323 test_blob(function() {
    324  return new Blob([
    325    new BigInt64Array([BigInt("0x5353415053534150")]),
    326    new BigUint64Array([BigInt("0x5353415053534150")])
    327  ]);
    328 }, {
    329  expected: "PASSPASSPASSPASS",
    330  type: "",
    331  desc: "Passing BigInt typed arrays as elements of the blobParts array should work."
    332 });
    333 
    334 var t_ports = async_test("Passing a FrozenArray as the blobParts array should work (FrozenArray<MessagePort>).");
    335 t_ports.step(function() {
    336    var channel = new MessageChannel();
    337    channel.port2.onmessage = this.step_func(function(e) {
    338        var b_ports = new Blob(e.ports);
    339        assert_equals(b_ports.size, "[object MessagePort]".length);
    340        this.done();
    341    });
    342    var channel2 = new MessageChannel();
    343    channel.port1.postMessage('', [channel2.port1]);
    344 });
    345 
    346 test_blob(function() {
    347  var blob = new Blob(['foo']);
    348  return new Blob([blob, blob]);
    349 }, {
    350  expected: "foofoo",
    351  type: "",
    352  desc: "Array with two blobs"
    353 });
    354 
    355 test_blob_binary(function() {
    356  var view = new Uint8Array([0, 255, 0]);
    357  return new Blob([view.buffer, view.buffer]);
    358 }, {
    359  expected: [0, 255, 0, 0, 255, 0],
    360  type: "",
    361  desc: "Array with two buffers"
    362 });
    363 
    364 test_blob_binary(function() {
    365  var view = new Uint8Array([0, 255, 0, 4]);
    366  var blob = new Blob([view, view]);
    367  assert_equals(blob.size, 8);
    368  var view1 = new Uint16Array(view.buffer, 2);
    369  return new Blob([view1, view.buffer, view1]);
    370 }, {
    371  expected: [0, 4, 0, 255, 0, 4, 0, 4],
    372  type: "",
    373  desc: "Array with two bufferviews"
    374 });
    375 
    376 test_blob(function() {
    377  var view = new Uint8Array([0]);
    378  var blob = new Blob(["fo"]);
    379  return new Blob([view.buffer, blob, "foo"]);
    380 }, {
    381  expected: "\0fofoo",
    382  type: "",
    383  desc: "Array with mixed types"
    384 });
    385 
    386 test(function() {
    387  const accessed = [];
    388  const stringified = [];
    389 
    390  new Blob([], {
    391    get type() { accessed.push('type'); },
    392    get endings() { accessed.push('endings'); }
    393  });
    394  new Blob([], {
    395    type: { toString: () => { stringified.push('type'); return ''; } },
    396    endings: { toString: () => { stringified.push('endings'); return 'transparent'; } }
    397  });
    398  assert_array_equals(accessed, ['endings', 'type']);
    399  assert_array_equals(stringified, ['endings', 'type']);
    400 }, "options properties should be accessed in lexicographic order.");
    401 
    402 test(function() {
    403  assert_throws_exactly(test_error, function() {
    404    new Blob(
    405      [{ toString: function() { throw test_error } }],
    406      {
    407        get type() { assert_unreached("type getter should not be called."); }
    408      }
    409    );
    410  });
    411 }, "Arguments should be evaluated from left to right.");
    412 
    413 [
    414  null,
    415  undefined,
    416  {},
    417  { unrecognized: true },
    418  /regex/,
    419  function() {}
    420 ].forEach(function(arg, idx) {
    421  test_blob(function() {
    422    return new Blob([], arg);
    423  }, {
    424    expected: "",
    425    type: "",
    426    desc: "Passing " + format_value(arg) + " (index " + idx + ") for options should use the defaults."
    427  });
    428  test_blob(function() {
    429    return new Blob(["\na\r\nb\n\rc\r"], arg);
    430  }, {
    431    expected: "\na\r\nb\n\rc\r",
    432    type: "",
    433    desc: "Passing " + format_value(arg) + " (index " + idx + ") for options should use the defaults (with newlines)."
    434  });
    435 });
    436 
    437 [
    438  123,
    439  123.4,
    440  true,
    441  'abc'
    442 ].forEach(arg => {
    443  test(t => {
    444    assert_throws_js(TypeError, () => new Blob([], arg),
    445                     'Blob constructor should throw with invalid property bag');
    446  }, `Passing ${JSON.stringify(arg)} for options should throw`);
    447 });
    448 
    449 var type_tests = [
    450  // blobParts, type, expected type
    451  [[], '', ''],
    452  [[], 'a', 'a'],
    453  [[], 'A', 'a'],
    454  [[], 'text/html', 'text/html'],
    455  [[], 'TEXT/HTML', 'text/html'],
    456  [[], 'text/plain;charset=utf-8', 'text/plain;charset=utf-8'],
    457  [[], '\u00E5', ''],
    458  [[], '\uD801\uDC7E', ''], // U+1047E
    459  [[], ' image/gif ', ' image/gif '],
    460  [[], '\timage/gif\t', ''],
    461  [[], 'image/gif;\u007f', ''],
    462  [[], '\u0130mage/gif', ''], // uppercase i with dot
    463  [[], '\u0131mage/gif', ''], // lowercase dotless i
    464  [[], 'image/gif\u0000', ''],
    465  // check that type isn't changed based on sniffing
    466  [[0x3C, 0x48, 0x54, 0x4D, 0x4C, 0x3E], 'unknown/unknown', 'unknown/unknown'], // "<HTML>"
    467  [[0x00, 0xFF], 'text/plain', 'text/plain'],
    468  [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], 'image/png', 'image/png'], // "GIF89a"
    469 ];
    470 
    471 type_tests.forEach(function(t) {
    472  test(function() {
    473    var arr = new Uint8Array([t[0]]).buffer;
    474    var b = new Blob([arr], {type:t[1]});
    475    assert_equals(b.type, t[2]);
    476  }, "Blob with type " + format_value(t[1]));
    477 });