blob: cc57ae1328494247bbd2340ada0effd07806e800 [file] [log] [blame]
<!DOCTYPE html>
<div id=result>NOTHING</div>
<script>
// Test that ReadableStream and WritableStream still work correctly when the
// global object has been interfered with.
//
// This test cannot use testharness.js because it doesn't work when you remove
// all the global objects.
'use strict';
const testRunner = self.testRunner;
if (testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}
async function runTest() {
const ReadableStream = self.ReadableStream;
const WritableStream = self.WritableStream;
const ByteLengthQueuingStrategy = self.ByteLengthQueuingStrategy;
const CountQueuingStrategy = self.CountQueuingStrategy;
const Error = self.Error;
const Promise = self.Promise;
const Object = self.Object;
const Object_getOwnPropertyNames = Object.getOwnPropertyNames;
function undefineObjectProperties(o, exceptions = new Set()) {
for (const key of Object_getOwnPropertyNames(o)) {
if (exceptions.has(key)) {
continue;
}
try {
o[key] = undefined;
} catch (e) {
assert(e.name === 'TypeError', 'expecting a TypeError');
}
}
}
function assert(pred, message) {
if (pred) {
return;
}
throw new Error(message);
}
async function testReadAndPipeTo() {
let done;
const rs = new ReadableStream({
pull(controller) {
if (done) {
controller.close();
}
assert(controller.desiredSize > 0, 'desiredSize must be greater than 0');
controller.enqueue('tree');
}
});
const reader = rs.getReader();
assert(rs.locked, 'ReadableStream should be locked');
await reader.read();
reader.releaseLock();
await rs.pipeTo(new WritableStream({
write() {
done = true;
}
}));
}
async function testReadableStreamClosed() {
const rs = new ReadableStream({
pull(controller) {
controller.close();
}
});
const reader = rs.getReader();
await reader.read();
await reader.closed
}
async function testPipeThrough() {
const emptyRS = new ReadableStream({
pull(controller) {
controller.close();
}
});
let resolve;
const p = new Promise(r => {resolve = r});
const closeResolvesWS = new WritableStream({
close() {
resolve();
}
}, new CountQueuingStrategy({ highWaterMark: 10}));
emptyRS.pipeThrough({
writable: closeResolvesWS,
readable: new ReadableStream()
}, {
preventClose: 0,
preventAbort: true,
preventCancel: 'yes'
});
await p;
}
async function testReadableStreamError() {
const rs = new ReadableStream({
pull(controller) {
controller.error('my error');
}
});
const reader = rs.getReader();
try {
await reader.read();
} catch (e) {
assert(e === 'my error', 'my error should be thrown');
}
}
async function testTee() {
const rs = new ReadableStream({
start(controller) {
controller.enqueue('beef');
}
}, { size: () => 1, highWaterMark: 19 });
const [left, right] = rs.tee();
const left_cancel = left.cancel();
const reader = right.getReader();
const { value, done } = await reader.read();
assert(value === 'beef', 'value should be beef');
assert(done === false, 'done should be false');
await reader.cancel();
await left_cancel;
}
async function testWritableStreamWriteAndReleaseLock() {
const ws = new WritableStream({}, {highWaterMark: 7});
assert(!ws.locked, 'ws should not be locked');
const writer = ws.getWriter();
assert(writer.desiredSize > 0, 'desiredSize should be positive');
await writer.ready;
await writer.write('something');
writer.releaseLock();
await ws.abort();
}
async function testWritableStreamClose() {
const ws = new WritableStream({}, new ByteLengthQueuingStrategy({ highWaterMark: 1024 }));
const writer = ws.getWriter();
await writer.close();
await writer.closed;
}
async function testWritableStreamDefaultControllerError() {
const ws = new WritableStream({
start(controller) {
controller.error('something bad');
}
});
try {
await ws.getWriter().closed;
} catch (e) {
assert(e === 'something bad', 'e should be something bad');
}
}
// Undefine various prototypes and static methods that might be used in the
// implementation.
// The "await" keyword uses Promise.prototype.then.
// TODO(ricea): Remove all uses of await from this test.
undefineObjectProperties(Promise.prototype, new Set(['then']));
undefineObjectProperties(Promise);
undefineObjectProperties(Object.prototype);
undefineObjectProperties(Object);
// Array.prototype.length is writable but assigning |undefined| to it results
// in a RangeError being thrown.
undefineObjectProperties(Array.prototype, new Set(['length']));
undefineObjectProperties(Array);
undefineObjectProperties(Function.prototype);
undefineObjectProperties(Function);
undefineObjectProperties(Number.prototype);
undefineObjectProperties(Number);
undefineObjectProperties(String.prototype);
undefineObjectProperties(String);
undefineObjectProperties(Boolean.prototype);
undefineObjectProperties(Boolean);
undefineObjectProperties(Math);
// Make methods that can be called during implicit type conversions throw.
Object.prototype.toString = () => {
throw new Error('Object toString() called');
};
Object.prototype.valueOf = () => {
throw new Error('Object valueOf() called');
};
Array.prototype.toString = () => {
throw new Error('Array toString() called');
};
Array.prototype.valueOf = () => {
throw new Error('Array valueOf() called');
};
// Undefine (almost) everything on the global object.
// Assigning to 'location' causes a navigation which breaks the test.
const doNotSet = new Set(['location']);
undefineObjectProperties(self, doNotSet);
await testReadAndPipeTo();
await testReadableStreamClosed();
await testPipeThrough();
await testReadableStreamError();
await testTee();
await testWritableStreamWriteAndReleaseLock();
await testWritableStreamClose();
await testWritableStreamDefaultControllerError();
document.querySelector('#result').textContent = 'PASS';
if (testRunner) {
testRunner.notifyDone();
}
}
runTest();
</script>