| <!DOCTYPE html> | |
| <meta charset=utf-8> | |
| <title>Blob constructor</title> | |
| <link rel=help href="http://dev.w3.org/2006/webapi/FileAPI/#constructorBlob"> | |
| <link rel=help href="https://heycam.github.io/webidl/#es-union"> | |
| <link rel=help href="https://heycam.github.io/webidl/#es-dictionary"> | |
| <link rel=help href="https://heycam.github.io/webidl/#es-sequence"> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script src="../support/Blob.js"></script> | |
| <p><strong><a href="https://www.w3.org/Bugs/Public/show_bug.cgi?id=23683">Discussion</a> | |
| is ongoing that will affect a number of the following tests.</strong> | |
| <div id="log"></div> | |
| <!-- used by "platform object that supports indexed properties" tests --> | |
| <iframe style="display:none"></iframe> | |
| <script> | |
| test(function() { | |
| assert_true("Blob" in window, "window should have a Blob property."); | |
| assert_equals(Blob.length, 0, "Blob.length should be 0."); | |
| assert_true(Blob instanceof Function, "Blob should be a function."); | |
| }, "Blob interface object"); | |
| // Step 1. | |
| test(function() { | |
| var blob = new Blob(); | |
| assert_true(blob instanceof Blob); | |
| assert_equals(String(blob), '[object Blob]'); | |
| assert_equals(blob.size, 0); | |
| assert_equals(blob.type, ""); | |
| }, "no-argument Blob constructor"); | |
| test(function() { | |
| assert_throws(new TypeError(), function() { var blob = Blob(); }); | |
| }, "no-argument Blob constructor without 'new'"); | |
| test(function() { | |
| var blob = new Blob; | |
| assert_true(blob instanceof Blob); | |
| assert_equals(blob.size, 0); | |
| assert_equals(blob.type, ""); | |
| }, "no-argument Blob constructor without brackets"); | |
| // blobParts argument (WebIDL). | |
| test(function() { | |
| var args = [ | |
| null, | |
| undefined, | |
| true, | |
| false, | |
| 0, | |
| 1, | |
| 1.5, | |
| "FAIL", | |
| new Date(), | |
| new RegExp(), | |
| ]; | |
| args.forEach(function(arg) { | |
| assert_throws(new TypeError(), function() { | |
| new Blob(arg); | |
| }, "Should throw for argument " + format_value(arg) + "."); | |
| }); | |
| }, "Passing non-objects, Dates and RegExps for blobParts should throw a TypeError."); | |
| test_blob(function() { | |
| return new Blob({}); | |
| }, { | |
| expected: "", | |
| type: "", | |
| desc: "A plain object should be treated as a sequence for the blobParts argument." | |
| }); | |
| test_blob(function() { | |
| return new Blob({ 0: "PASS", length: 1 }); | |
| }, { | |
| expected: "PASS", | |
| type: "", | |
| desc: "A plain object with a length property should be treated as a sequence for the blobParts argument." | |
| }); | |
| test_blob(function() { | |
| return new Blob(new String("xyz")); | |
| }, { | |
| expected: "xyz", | |
| type: "", | |
| desc: "A String object should be treated as a sequence for the blobParts argument." | |
| }); | |
| test_blob(function() { | |
| return new Blob(new Uint8Array([1, 2, 3])); | |
| }, { | |
| expected: "123", | |
| type: "", | |
| desc: "A Uint8Array object should be treated as a sequence for the blobParts argument." | |
| }); | |
| var test_error = { name: "test" }; | |
| test(function() { | |
| var obj = { | |
| get length() { throw test_error; } | |
| }; | |
| assert_throws(test_error, function() { | |
| new Blob(obj); | |
| }); | |
| }, "The length getter should be invoked and any exceptions should be propagated."); | |
| test_blob(function() { | |
| var element = document.createElement("div"); | |
| element.appendChild(document.createElement("div")); | |
| element.appendChild(document.createElement("p")); | |
| var list = element.children; | |
| Object.defineProperty(list, "length", { | |
| get: function() { throw test_error; } | |
| }); | |
| return new Blob(list); | |
| }, { | |
| expected: "[object HTMLDivElement][object HTMLParagraphElement]", | |
| type: "", | |
| desc: "A platform object that supports indexed properties should be treated as a sequence for the blobParts argument (overwritten 'length'.)" | |
| }); | |
| test(function() { | |
| assert_throws(test_error, function() { | |
| var obj = { | |
| length: { | |
| valueOf: null, | |
| toString: function() { throw test_error; } | |
| } | |
| }; | |
| new Blob(obj); | |
| }); | |
| assert_throws(test_error, function() { | |
| var obj = { | |
| length: { valueOf: function() { throw test_error; } } | |
| }; | |
| new Blob(obj); | |
| }); | |
| }, "ToUint32 should be applied to the length and any exceptions should be propagated."); | |
| test(function() { | |
| var received = []; | |
| var obj = { | |
| get length() { | |
| received.push("length getter"); | |
| return { | |
| valueOf: function() { | |
| received.push("length valueOf"); | |
| return 3; | |
| } | |
| }; | |
| }, | |
| get 0() { | |
| received.push("0 getter"); | |
| return { | |
| toString: function() { | |
| received.push("0 toString"); | |
| return "a"; | |
| } | |
| }; | |
| }, | |
| get 1() { | |
| received.push("1 getter"); | |
| throw test_error; | |
| }, | |
| get 2() { | |
| received.push("2 getter"); | |
| assert_unreached("Should not call the getter for 2 if the getter for 1 threw."); | |
| } | |
| }; | |
| assert_throws(test_error, function() { | |
| new Blob(obj); | |
| }); | |
| assert_array_equals(received, [ | |
| "length getter", | |
| "length valueOf", | |
| "0 getter", | |
| "0 toString", | |
| "1 getter" | |
| ]); | |
| }, "Getters and value conversions should happen in order until an exception is thrown."); | |
| // XXX should add tests edge cases of ToUint32(length) | |
| test(function() { | |
| assert_throws(test_error, function() { | |
| new Blob([{ toString: function() { throw test_error; } }]); | |
| }, "Throwing toString"); | |
| assert_throws(test_error, function() { | |
| new Blob([{ toString: undefined, valueOf: function() { throw test_error; } }]); | |
| }, "Throwing valueOf"); | |
| assert_throws(test_error, function() { | |
| new Blob([{ | |
| toString: function() { throw test_error; }, | |
| valueOf: function() { assert_unreached("Should not call valueOf if toString is present."); } | |
| }]); | |
| }, "Throwing toString and valueOf"); | |
| assert_throws(new TypeError(), function() { | |
| new Blob([{toString: null, valueOf: null}]); | |
| }, "Null toString and valueOf"); | |
| }, "ToString should be called on elements of the blobParts array and any exceptions should be propagated."); | |
| test_blob(function() { | |
| var arr = [ | |
| { toString: function() { arr.pop(); return "PASS"; } }, | |
| { toString: function() { assert_unreached("Should have removed the second element of the array rather than called toString() on it."); } } | |
| ]; | |
| return new Blob(arr); | |
| }, { | |
| expected: "PASSundefined", | |
| type: "", | |
| desc: "Changes to the blobParts array should be reflected in the returned Blob (pop)." | |
| }); | |
| test_blob(function() { | |
| var arr = [ | |
| { | |
| toString: function() { | |
| if (arr.length === 3) { | |
| return "SS"; | |
| } | |
| arr.unshift({ | |
| toString: function() { | |
| assert_unreached("Should only access index 0 once."); | |
| } | |
| }); | |
| return "PA"; | |
| } | |
| }, | |
| { | |
| toString: function() { | |
| assert_unreached("Should not access the final element."); | |
| } | |
| } | |
| ]; | |
| return new Blob(arr); | |
| }, { | |
| expected: "PASS", | |
| type: "", | |
| desc: "Changes to the blobParts array should be reflected in the returned Blob (unshift)." | |
| }); | |
| test_blob(function() { | |
| // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17652 | |
| return new Blob([ | |
| null, | |
| undefined, | |
| true, | |
| false, | |
| 0, | |
| 1, | |
| new String("stringobject"), | |
| [], | |
| ['x', 'y'], | |
| {}, | |
| { 0: "FAIL", length: 1 }, | |
| { toString: function() { return "stringA"; } }, | |
| { toString: undefined, valueOf: function() { return "stringB"; } }, | |
| { valueOf: function() { assert_unreached("Should not call valueOf if toString is present on the prototype."); } } | |
| ]); | |
| }, { | |
| expected: "nullundefinedtruefalse01stringobjectx,y[object Object][object Object]stringAstringB[object Object]", | |
| type: "", | |
| desc: "ToString should be called on elements of the blobParts array." | |
| }); | |
| test_blob(function() { | |
| return new Blob([ | |
| new ArrayBuffer(8) | |
| ]); | |
| }, { | |
| expected: "\0\0\0\0\0\0\0\0", | |
| type: "", | |
| desc: "ArrayBuffer elements of the blobParts array should be supported." | |
| }); | |
| test_blob(function() { | |
| return new Blob([ | |
| new Uint8Array([0x50, 0x41, 0x53, 0x53]), | |
| new Int8Array([0x50, 0x41, 0x53, 0x53]), | |
| new Uint16Array([0x4150, 0x5353]), | |
| new Int16Array([0x4150, 0x5353]), | |
| new Uint32Array([0x53534150]), | |
| new Int32Array([0x53534150]), | |
| new Float32Array([0xD341500000]) | |
| ]); | |
| }, { | |
| expected: "PASSPASSPASSPASSPASSPASSPASS", | |
| type: "", | |
| desc: "Passing typed arrays as elements of the blobParts array should work." | |
| }); | |
| test_blob(function() { | |
| return new Blob([ | |
| // 0x535 3415053534150 | |
| // 0x535 = 0b010100110101 -> Sign = +, Exponent = 1333 - 1023 = 310 | |
| // 0x13415053534150 * 2**(-52) | |
| // ==> 0x13415053534150 * 2**258 = 2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680 | |
| new Float64Array([2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680]) | |
| ]); | |
| }, { | |
| expected: "PASSPASS", | |
| type: "", | |
| desc: "Passing a Float64Array as element of the blobParts array should work." | |
| }); | |
| test_blob(function() { | |
| return new Blob(document.createElement("div")); | |
| }, { | |
| expected: "", | |
| type: "", | |
| desc: "Passing an element as the blobParts array should work." | |
| }); | |
| test_blob(function() { | |
| return new Blob(window); | |
| }, { | |
| expected: "[object Window]", | |
| type: "", | |
| desc: "Passing an platform object that supports indexed properties as the blobParts array should work (window)." | |
| }); | |
| test_blob(function() { | |
| window[0].toString = function() { return "foo"; }; | |
| return new Blob(window); | |
| }, { | |
| expected: "foo", | |
| type: "", | |
| desc: "Passing an platform object that supports indexed properties as the blobParts array should work (window with custom toString)." | |
| }); | |
| test_blob(function() { | |
| var select = document.createElement("select"); | |
| select.appendChild(document.createElement("option")); | |
| return new Blob(select); | |
| }, { | |
| expected: "[object HTMLOptionElement]", | |
| type: "", | |
| desc: "Passing an platform object that supports indexed properties as the blobParts array should work (select)." | |
| }); | |
| var t_ports = async_test("Passing a platform array object as the blobParts array should work (MessagePort[])."); | |
| t_ports.step(function() { | |
| var channel = new MessageChannel(); | |
| channel.port2.onmessage = this.step_func(function(e) { | |
| var b_ports = new Blob(e.ports); | |
| assert_equals(b_ports.size, "[object MessagePort]".length); | |
| this.done(); | |
| }); | |
| var channel2 = new MessageChannel(); | |
| channel.port1.postMessage('', [channel2.port1]); | |
| }); | |
| test_blob(function() { | |
| var elm = document.createElement("div"); | |
| elm.setAttribute("foo", "bar"); | |
| return new Blob(elm.attributes); | |
| }, { | |
| expected: "[object Attr]", | |
| type: "", | |
| desc: "Passing a platform array object as the blobParts array should work (Attr[])." | |
| }); | |
| test_blob(function() { | |
| var blob = new Blob(['foo']); | |
| return new Blob([blob, blob]); | |
| }, { | |
| expected: "foofoo", | |
| type: "", | |
| desc: "Array with two blobs" | |
| }); | |
| test_blob_binary(function() { | |
| var view = new Uint8Array([0, 255, 0]); | |
| return new Blob([view.buffer, view.buffer]); | |
| }, { | |
| expected: [0, 255, 0, 0, 255, 0], | |
| type: "", | |
| desc: "Array with two buffers" | |
| }); | |
| test_blob_binary(function() { | |
| var view = new Uint8Array([0, 255, 0, 4]); | |
| var blob = new Blob([view, view]); | |
| assert_equals(blob.size, 8); | |
| var view1 = new Uint16Array(view.buffer, 2); | |
| return new Blob([view1, view.buffer, view1]); | |
| }, { | |
| expected: [0, 4, 0, 255, 0, 4, 0, 4], | |
| type: "", | |
| desc: "Array with two bufferviews" | |
| }); | |
| test_blob(function() { | |
| var view = new Uint8Array([0]); | |
| var blob = new Blob(["fo"]); | |
| return new Blob([view.buffer, blob, "foo"]); | |
| }, { | |
| expected: "\0fofoo", | |
| type: "", | |
| desc: "Array with mixed types" | |
| }); | |
| // options argument | |
| test(function() { | |
| new Blob([], { endings: "invalidEnumValue" }); | |
| new Blob([], { endings: null }); | |
| new Blob([], { endings: undefined }); | |
| new Blob([], { endings: 0 }); | |
| new Blob([], { get endings() { assert_unreached("Should not call getter"); } }); | |
| }, "The 'endings' property should be ignored."); | |
| test(function() { | |
| assert_throws(test_error, function() { | |
| new Blob([], { | |
| get type() { throw test_error; } | |
| }); | |
| }); | |
| assert_throws(test_error, function() { | |
| new Blob([], { | |
| type: { toString: function() { throw test_error; } } | |
| }); | |
| }); | |
| }, "options properties should be accessed in lexicographic order."); | |
| test(function() { | |
| assert_throws(test_error, function() { | |
| new Blob( | |
| [{ toString: function() { throw test_error } }], | |
| { | |
| get type() { assert_unreached("type getter should not be called."); } | |
| } | |
| ); | |
| }); | |
| }, "Arguments should be evaluated from left to right."); | |
| [ | |
| null, | |
| undefined, | |
| {}, | |
| { unrecognized: true }, | |
| /regex/, | |
| function() {} | |
| ].forEach(function(arg, idx) { | |
| test_blob(function() { | |
| return new Blob([], arg); | |
| }, { | |
| expected: "", | |
| type: "", | |
| desc: "Passing " + format_value(arg) + " (index " + idx + ") for options should use the defaults." | |
| }); | |
| test_blob(function() { | |
| return new Blob(["\na\r\nb\n\rc\r"], arg); | |
| }, { | |
| expected: "\na\r\nb\n\rc\r", | |
| type: "", | |
| desc: "Passing " + format_value(arg) + " (index " + idx + ") for options should use the defaults (with newlines)." | |
| }); | |
| }); | |
| test_blob(function() { | |
| return new Blob(["\na\r\nb\n\rc\r"], { endings: "transparent" }); | |
| }, { | |
| expected: "\na\r\nb\n\rc\r", | |
| type: "", | |
| desc: "Newlines should not change when endings is 'transparent'." | |
| }); | |
| test_blob(function() { | |
| return new Blob(["\na\r\nb\n\rc\r"], { endings: "native" }); | |
| }, { | |
| expected: "\na\r\nb\n\rc\r", | |
| type: "", | |
| desc: "Newlines should not change when endings is 'native'." | |
| }); | |
| var type_tests = [ | |
| // blobParts, type, expected type | |
| [[], '', ''], | |
| [[], 'a', 'a'], | |
| [[], 'A', 'a'], | |
| [[], 'text/html', 'text/html'], | |
| [[], 'TEXT/HTML', 'text/html'], | |
| [[], '\u00E5', ''], | |
| [[], '\uD801\uDC7E', ''], // U+1047E | |
| [[], ' image/gif ', ' image/gif '], | |
| [[], '\timage/gif\t', ''], | |
| [[], 'image/gif;\u007f', ''], | |
| [[], '\u0130mage/gif', ''], // uppercase i with dot | |
| [[], '\u0131mage/gif', ''], // lowercase dotless i | |
| [[], 'image/gif\u0000', ''], | |
| // check that type isn't changed based on sniffing | |
| [[0x3C, 0x48, 0x54, 0x4D, 0x4C, 0x3E], 'unknown/unknown', 'unknown/unknown'], // "<HTML>" | |
| [[0x00, 0xFF], 'text/plain', 'text/plain'], | |
| [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], 'image/png', 'image/png'], // "GIF89a" | |
| ]; | |
| type_tests.forEach(function(t) { | |
| test(function() { | |
| var arr = new Uint8Array([t[0]]).buffer; | |
| var b = new Blob([arr], {type:t[1]}); | |
| assert_equals(b.type, t[2]); | |
| }, "Blob with type " + format_value(t[1])); | |
| }); | |
| </script> |