Skip to content

Instantly share code, notes, and snippets.

@mkruisselbrink
Last active December 7, 2016 22:55
Show Gist options
  • Save mkruisselbrink/2dbfb08082699fe593d6047491666667 to your computer and use it in GitHub Desktop.
Save mkruisselbrink/2dbfb08082699fe593d6047491666667 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<meta charset=utf-8>
<title>Blob.close</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
if (!Blob.prototype.close && Blob.prototype.msClose) {
// Edge has msClose rather than close.
Blob.prototype.close = Blob.prototype.msClose;
// Edge also doesn't have isClosed, approximate it with size == 0
Object.defineProperty(Blob.prototype, 'isClosed', {
get: function() { return this.size == 0; }
});
// Edge throws different types of exceptions, so for now just ignore
// the exact type it throws.
const oldAssertThrows = assert_throws;
assert_throws = function(e, f, d) { return oldAssertThrows(null, f, d); };
const oldPromiseRejects = promise_rejects;
promise_rejects = function(t, e, p, d) { return oldPromiseRejects(t, null, p, d); };
}
// Edge doesn't implement FormData.get, so polyfill it by parsing
// the textual output of FormData.
function parseFormDataText(s) {
const lines = s.split('\r\n');
const boundary = lines[0];
let props = [];
let current = null;
let inHeaders = false;
for (let line of lines) {
if (line.startsWith(boundary)) {
inHeaders = true;
if (current) {
current.value = current.value.substr(0, current.value.length-1);
props.push(current);
}
current = {value : ''};
continue;
}
if (inHeaders) {
if (line == '') {
inHeaders = false;
continue;
}
if (line.startsWith('Content-Disposition')) {
let params = line.split(';');
for (let param of params) {
let kv = param.trim().split('=');
if (kv[0] == 'name')
current.name = kv[1].substr(1, kv[1].length - 2);
}
}
continue;
}
current.value = current.value + line + '\n';
}
return props;
}
// Wrapper around FormData.get that returns a promise.
function FormDataGet(fd, name) {
if (FormData.prototype.get) {
return Promise.resolve(fd.get(name));
} else {
return new Response(fd).text().then(t => {
let props = parseFormDataText(t);
for (let p of props) {
if (p.name == name) return new Blob([p.value]);
}
return null;
});
}
}
function readBlob(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onerror = () => reject(reader.error);
reader.onload = () => resolve(reader.result);
reader.readAsText(blob, 'UTF-8');
});
}
function unreached_rejection(test, prefix) {
return test.step_func(function(error) {
var reason = error.message || error.name || error;
var error_prefix = prefix || 'unexpected rejection';
assert_unreached(error_prefix + ': ' + reason);
});
}
test(t => {
const blob = new Blob(["TEST"]);
assert_false(blob.isClosed);
blob.close();
assert_equals(0, blob.size);
assert_true(blob.isClosed);
}, 'A closed blob has correct attributes.');
test(t => {
const blob = new Blob(["TEST"]);
blob.close();
const reader = new FileReader();
assert_throws('InvalidStateError', () => reader.readAsText(blob, 'UTF-8'));
}, 'Reading a closed blob should fail.');
test(t => {
const blob = new Blob(["TEST"]);
blob.close();
assert_throws('InvalidStateError', () => blob.slice());
}, 'Slicing a closed blob should fail.');
test(t => {
const blob = new Blob(["TEST"]);
blob.close();
assert_throws('InvalidStateError', () => new Blob([blob]));
}, 'Referencing a closed blob should fail.');
promise_test(t => {
const blob = new Blob(["TEST"]);
const url = URL.createObjectURL(blob);
blob.close();
return promise_rejects(t, new TypeError, fetch(url));
}, 'Can\'t read a closed blob through a previously created URL.');
test(t => {
const blob = new Blob(["TEST"]);
blob.close();
assert_throws('InvalidStateError', () => URL.createObjectURL(blob));
}, 'Can\'t create a blob URL for a closed blob.');
promise_test(t => {
const blob = new Blob(["TEST"]);
const sliced = blob.slice();
blob.close();
return readBlob(sliced).then(result => assert_equals(result, 'TEST'));
}, 'Slice should still have blob data.');
async_test(t => {
const blob = new Blob(["TEST"]);
const channel = new MessageChannel();
channel.port1.onmessage = t.step_func(e => {
const cloned = e.data;
assert_true(blob.isClosed);
assert_false(cloned.isClosed);
readBlob(cloned).then(t.step_func_done(result => assert_equals(result, 'TEST'))).catch(unreached_rejection(t));
});
channel.port2.postMessage(blob);
blob.close();
}, 'Clone should still have blob data.');
test(t => {
const blob = new Blob(["TEST"]);
blob.close();
const channel = new MessageChannel();
assert_throws('DataCloneError', () => channel.port2.postMessage(blob));
}, 'Cloning after closing should not be possible.');
promise_test(t => {
const blob = new Blob(["TEST"]);
blob.close();
const request = new Request('http://example.com', {method: 'POST', body: blob})
return request.text().then(text => assert_equals(text, ''));
}, 'Creating a request with a closed blob should not fail.');
promise_test(t => {
const blob = new Blob(["TEST"]);
blob.close();
const request = new Request('/XMLHttpRequest/resources/content.py', {method: 'POST', body: blob})
return fetch(request).then(response => response.text()).then(text => assert_equals(text, ''));
}, 'Fetching a request with a closed blob should not fail.');
test(t => {
const blob = new Blob(["TEST"]);
blob.close();
const response = new Response(blob);
return response.text().then(text => assert_equals(text, ''));
}, 'Creating a response with a closed blob should not fail.');
promise_test(t => {
const blob = new Blob(["TEST"]);
const request = new Request('http://example.com', {method: 'POST', body: blob});
blob.close();
return request.text().then(text => assert_equals(text, 'TEST'));
}, 'Request can still read body after closing blob.');
promise_test(t => {
const blob = new Blob(["TEST"]);
const response = new Response(blob);
blob.close();
return response.text().then(text => assert_equals(text, 'TEST'));
}, 'Response can still read body after closing blob.');
test(t => {
const blob = new Blob(["TEST"]);
blob.close();
const data = new FormData();
assert_throws('InvalidStateError', () => data.append('foo', blob));
assert_throws('InvalidStateError', () => data.set('foo', blob));
}, 'FormData should throw when using closed blobs.');
promise_test(t => {
const blob = new Blob(["TEST"]);
const data = new FormData();
data.append('foo', blob);
blob.close();
return FormDataGet(data, 'foo').then(b => {
assert_false(b.isClosed);
return readBlob(b);
}).then(result => assert_equals(result, 'TEST'));
}, 'FormData should keep data after closing blobs.');
promise_test(t => {
const blob = new Blob(["TEST"]);
const data = new FormData();
data.append('foo', blob);
return FormDataGet(data, 'foo').then(b => {
b.close();
return FormDataGet(data, 'foo');
}).then(b => {
assert_false(b.isClosed);
return readBlob(b);
}).then(result => assert_equals(result, 'TEST'));
}, 'Closing result of FormData.get() should not clear out data.');
async_test(t => {
const xhr = new XMLHttpRequest();
const blob = new Blob(["TEST"]);
blob.close();
xhr.onreadystatechange = t.step_func(() => {
if (xhr.readyState == 4) {
assert_equals(xhr.status, 200);
assert_equals(xhr.response, "");
t.done();
}
});
xhr.open("POST", "/XMLHttpRequest/resources/content.py");
xhr.send(blob);
}, 'Sending closed blob using XHR.');
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment