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