Import Cobalt 19.master.0.203780

Includes the following patches:
  https://cobalt-review.googlesource.com/c/cobalt/+/5210
    by errong.leng@samsung.com
  https://cobalt-review.googlesource.com/c/cobalt/+/5270
    by linus.wang@samsung.com
diff --git a/src/third_party/web_platform_tests/FileAPI/blob/Blob-XHR-revoke.html b/src/third_party/web_platform_tests/FileAPI/blob/Blob-XHR-revoke.html
new file mode 100644
index 0000000..fea313e
--- /dev/null
+++ b/src/third_party/web_platform_tests/FileAPI/blob/Blob-XHR-revoke.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<title>Revoking blob URL used with XMLHttpRequest</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+async_test(function(t) {
+    var blob = new Blob(["test"]);
+    var url = URL.createObjectURL(blob);
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", url);
+
+    // Revoke the object URL.  XHR should take a reference to the blob as soon as
+    // it receives it in open(), so the request succeeds even though we revoke the
+    // URL before calling send().
+    URL.revokeObjectURL(url);
+
+    xhr.send();
+
+    xhr.onload = t.step_func(function() {
+        assert_equals(xhr.response, "test");
+        t.done();
+    })
+    xhr.onerror = t.step_func(function() {
+        assert_unreached("Got unexpected error event");
+    })
+});
+</script>
\ No newline at end of file
diff --git a/src/third_party/web_platform_tests/FileAPI/blob/Blob-close.html b/src/third_party/web_platform_tests/FileAPI/blob/Blob-close.html
new file mode 100644
index 0000000..45df1e2
--- /dev/null
+++ b/src/third_party/web_platform_tests/FileAPI/blob/Blob-close.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Blob.close</title>
+<link rel=help href="http://dev.w3.org/2006/webapi/FileAPI/#dfn-close">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/Blob.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+  var blob = new Blob(["TEST"]);
+  var sliced = blob.slice();
+  blob.close();
+
+  async_test(function(t) {
+    var reader = new FileReader();
+
+    reader.onload = t.step_func(function(evt) {
+      assert_unreached("Should not dispatch the load event");
+    });
+
+    reader.onerror = t.step_func(function(e) {
+      assert_equals(reader.result, null);
+      assert_equals(reader.error.code, DOMException.INVALID_STATE_ERR);
+      t.done();
+    });
+
+    reader.readAsText(blob, "UTF-8");
+  }, "Closed Blob");
+
+  test_blob(function() {
+    return sliced;
+  }, {
+    expected: "TEST",
+    type: "",
+    desc: "Slice should still have the data."
+  });
+});
+</script>
diff --git a/src/third_party/web_platform_tests/FileAPI/blob/Blob-constructor.html b/src/third_party/web_platform_tests/FileAPI/blob/Blob-constructor.html
new file mode 100644
index 0000000..dced17d
--- /dev/null
+++ b/src/third_party/web_platform_tests/FileAPI/blob/Blob-constructor.html
@@ -0,0 +1,495 @@
+<!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>
diff --git a/src/third_party/web_platform_tests/FileAPI/blob/Blob-slice.html b/src/third_party/web_platform_tests/FileAPI/blob/Blob-slice.html
new file mode 100644
index 0000000..a66136b
--- /dev/null
+++ b/src/third_party/web_platform_tests/FileAPI/blob/Blob-slice.html
@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Blob slice</title>
+<link rel=help href="http://dev.w3.org/2006/webapi/FileAPI/#slice-method-algo">
+<link rel=author title="Saurabh Anand" href="mailto:saurabhanandiit@gmail.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/Blob.js"></script>
+<div id="log"></div>
+<script>
+test_blob(function() {
+  var blobTemp = new Blob(["PASS"]);
+  return blobTemp.slice();
+}, {
+  expected: "PASS",
+  type: "",
+  desc: "no-argument Blob slice"
+});
+
+test(function() {
+  var blob1, blob2;
+
+  test_blob(function() {
+    return blob1 = new Blob(["squiggle"]);
+  }, {
+    expected: "squiggle",
+    type: "",
+    desc: "blob1."
+  });
+
+  test_blob(function() {
+    return blob2 = new Blob(["steak"], {type: "content/type"});
+  }, {
+    expected: "steak",
+    type: "content/type",
+    desc: "blob2."
+  });
+
+  var arrayBuffer = new ArrayBuffer(16);
+  var int8View = new Int8Array(arrayBuffer);
+  for (var i = 0; i < 16; i++) {
+    int8View[i] = i + 65;
+  }
+
+  var testData = [
+    [
+      ["PASSSTRING"],
+      [{start:  -6, contents: "STRING"},
+       {start: -12, contents: "PASSSTRING"},
+       {start:   4, contents: "STRING"},
+       {start:  12, contents: ""},
+       {start: 0, end:  -6, contents: "PASS"},
+       {start: 0, end: -12, contents: ""},
+       {start: 0, end:   4, contents: "PASS"},
+       {start: 0, end:  12, contents: "PASSSTRING"},
+       {start: 7, end:   4, contents: ""}]
+    ],
+
+    // Test 3 strings
+    [
+      ["foo", "bar", "baz"],
+      [{start:  0, end:  9, contents: "foobarbaz"},
+       {start:  0, end:  3, contents: "foo"},
+       {start:  3, end:  9, contents: "barbaz"},
+       {start:  6, end:  9, contents: "baz"},
+       {start:  6, end: 12, contents: "baz"},
+       {start:  0, end:  9, contents: "foobarbaz"},
+       {start:  0, end: 11, contents: "foobarbaz"},
+       {start: 10, end: 15, contents: ""}]
+    ],
+
+    // Test string, Blob, string
+    [
+      ["foo", blob1, "baz"],
+      [{start:  0, end:  3, contents: "foo"},
+       {start:  3, end: 11, contents: "squiggle"},
+       {start:  2, end:  4, contents: "os"},
+       {start: 10, end: 12, contents: "eb"}]
+    ],
+
+    // Test blob, string, blob
+    [
+      [blob1, "foo", blob1],
+      [{start:  0, end:  8, contents: "squiggle"},
+       {start:  7, end:  9, contents: "ef"},
+       {start: 10, end: 12, contents: "os"},
+       {start:  1, end:  4, contents: "qui"},
+       {start: 12, end: 15, contents: "qui"},
+       {start: 40, end: 60, contents: ""}]
+    ],
+
+    // Test blobs all the way down
+    [
+      [blob2, blob1, blob2],
+      [{start: 0,  end:  5, contents: "steak"},
+       {start: 5,  end: 13, contents: "squiggle"},
+       {start: 13, end: 18, contents: "steak"},
+       {start:  1, end:  3, contents: "te"},
+       {start:  6, end: 10, contents: "quig"}]
+    ],
+
+    // Test an ArrayBufferView
+    [
+      [int8View, blob1, "foo"],
+      [{start:  0, end:  8, contents: "ABCDEFGH"},
+       {start:  8, end: 18, contents: "IJKLMNOPsq"},
+       {start: 17, end: 20, contents: "qui"},
+       {start:  4, end: 12, contents: "EFGHIJKL"}]
+    ],
+
+    // Test a partial ArrayBufferView
+    [
+      [new Uint8Array(arrayBuffer, 3, 5), blob1, "foo"],
+      [{start:  0, end:  8, contents: "DEFGHsqu"},
+       {start:  8, end: 18, contents: "igglefoo"},
+       {start:  4, end: 12, contents: "Hsquiggl"}]
+    ],
+
+    // Test type coercion of a number
+    [
+      [3, int8View, "foo"],
+      [{start:  0, end:  8, contents: "3ABCDEFG"},
+       {start:  8, end: 18, contents: "HIJKLMNOPf"},
+       {start: 17, end: 21, contents: "foo"},
+       {start:  4, end: 12, contents: "DEFGHIJK"}]
+    ],
+
+    [
+      [(new Uint8Array([0, 255, 0])).buffer,
+       new Blob(['abcd']),
+       'efgh',
+       'ijklmnopqrstuvwxyz'],
+      [{start:  1, end:  4, contents: "\uFFFD\u0000a"},
+       {start:  4, end:  8, contents: "bcde"},
+       {start:  8, end: 12, contents: "fghi"},
+       {start:  1, end: 12, contents: "\uFFFD\u0000abcdefghi"}]
+    ]
+  ];
+
+  testData.forEach(function(data, i) {
+    var blobs = data[0];
+    var tests = data[1];
+    tests.forEach(function(expectations, j) {
+      test(function() {
+        var blob = new Blob(blobs);
+        assert_true(blob instanceof Blob);
+        assert_false(blob instanceof File);
+
+        test_blob(function() {
+          return expectations.end === undefined
+                 ? blob.slice(expectations.start)
+                 : blob.slice(expectations.start, expectations.end);
+        }, {
+          expected: expectations.contents,
+          type: "",
+          desc: "Slicing test: slice (" + i + "," + j + ")."
+        });
+      }, "Slicing test (" + i + "," + j + ").");
+    });
+  });
+}, "Slices");
+
+var invalidTypes = [
+  "\xFF",
+  "te(xt/plain",
+  "te)xt/plain",
+  "te<xt/plain",
+  "te>xt/plain",
+  "te@xt/plain",
+  "te,xt/plain",
+  "te;xt/plain",
+  "te:xt/plain",
+  "te\\xt/plain",
+  "te\"xt/plain",
+  "te/xt/plain",
+  "te[xt/plain",
+  "te]xt/plain",
+  "te?xt/plain",
+  "te=xt/plain",
+  "te{xt/plain",
+  "te}xt/plain",
+  "te\x20xt/plain",
+  "te\x09xt/plain",
+  "te\x00xt/plain",
+  "te\x1Fxt/plain",
+  "te\x7Fxt/plain"
+];
+invalidTypes.forEach(function(type) {
+  test_blob(function() {
+    var blob = new Blob(["PASS"]);
+    return blob.slice(0, 4, type);
+  }, {
+    expected: "PASS",
+    type: "",
+    desc: "Invalid contentType (" + format_value(type) + ")"
+  });
+});
+
+var validTypes = [
+  "TEXT/PLAIN",
+  "text/plain;charset = UTF-8",
+  "text/plain;charset=UTF-8"
+];
+validTypes.forEach(function(type) {
+  test_blob(function() {
+    var blob = new Blob(["PASS"]);
+    return blob.slice(0, 4, type);
+  }, {
+    expected: "PASS",
+    type: type.toLowerCase(),
+    desc: "Valid contentType (" + format_value(type) + ")"
+  });
+});
+</script>