import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,6 @@
@domenic
@tyoshino
@yutakahirano
@youennf
@calvaris
@wanderview

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="resources/test-initializer.js"></script>
<script src="byte-length-queuing-strategy.js"></script>
<script>
'use strict';
worker_test('byte-length-queuing-strategy.js');
</script>

View file

@ -0,0 +1,107 @@
'use strict';
if (self.importScripts) {
self.importScripts('/resources/testharness.js');
}
test(() => {
new ByteLengthQueuingStrategy({ highWaterMark: 4 });
}, 'Can construct a ByteLengthQueuingStrategy with a valid high water mark');
test(() => {
for (const highWaterMark of [-Infinity, NaN, 'foo', {}, () => {}]) {
const strategy = new ByteLengthQueuingStrategy({ highWaterMark });
assert_equals(strategy.highWaterMark, highWaterMark, `${highWaterMark} gets set correctly`);
}
}, 'Can construct a ByteLengthQueuingStrategy with any value as its high water mark');
test(() => {
const highWaterMark = 1;
const highWaterMarkObjectGetter = {
get highWaterMark() { return highWaterMark; }
};
const error = new Error('wow!');
const highWaterMarkObjectGetterThrowing = {
get highWaterMark() { throw error; }
};
assert_throws({ name: 'TypeError' }, () => new ByteLengthQueuingStrategy(), 'construction fails with undefined');
assert_throws({ name: 'TypeError' }, () => new ByteLengthQueuingStrategy(null), 'construction fails with null');
assert_throws({ name: 'Error' }, () => new ByteLengthQueuingStrategy(highWaterMarkObjectGetterThrowing),
'construction fails with an object with a throwing highWaterMark getter');
// Should not fail:
new ByteLengthQueuingStrategy('potato');
new ByteLengthQueuingStrategy({});
new ByteLengthQueuingStrategy(highWaterMarkObjectGetter);
}, 'ByteLengthQueuingStrategy constructor behaves as expected with strange arguments');
test(() => {
const size = 1024;
const chunk = { byteLength: size };
const chunkGetter = {
get byteLength() { return size; }
};
const error = new Error('wow!');
const chunkGetterThrowing = {
get byteLength() { throw error; }
};
assert_throws({ name: 'TypeError' }, () => ByteLengthQueuingStrategy.prototype.size(), 'size fails with undefined');
assert_throws({ name: 'TypeError' }, () => ByteLengthQueuingStrategy.prototype.size(null), 'size fails with null');
assert_equals(ByteLengthQueuingStrategy.prototype.size('potato'), undefined,
'size succeeds with undefined with a random non-object type');
assert_equals(ByteLengthQueuingStrategy.prototype.size({}), undefined,
'size succeeds with undefined with an object without hwm property');
assert_equals(ByteLengthQueuingStrategy.prototype.size(chunk), size,
'size succeeds with the right amount with an object with a hwm');
assert_equals(ByteLengthQueuingStrategy.prototype.size(chunkGetter), size,
'size succeeds with the right amount with an object with a hwm getter');
assert_throws({ name: 'Error' }, () => ByteLengthQueuingStrategy.prototype.size(chunkGetterThrowing),
'size fails with the error thrown by the getter');
}, 'ByteLengthQueuingStrategy size behaves as expected with strange arguments');
test(() => {
const thisValue = null;
const returnValue = { 'returned from': 'byteLength getter' };
const chunk = {
get byteLength() { return returnValue; }
};
assert_equals(ByteLengthQueuingStrategy.prototype.size.call(thisValue, chunk), returnValue);
}, 'ByteLengthQueuingStrategy.prototype.size should work generically on its this and its arguments');
test(() => {
const strategy = new ByteLengthQueuingStrategy({ highWaterMark: 4 });
assert_object_equals(Object.getOwnPropertyDescriptor(strategy, 'highWaterMark'),
{ value: 4, writable: true, enumerable: true, configurable: true },
'highWaterMark property should be a data property with the value passed the constructor');
assert_equals(typeof strategy.size, 'function');
}, 'ByteLengthQueuingStrategy instances have the correct properties');
test(() => {
const strategy = new ByteLengthQueuingStrategy({ highWaterMark: 4 });
assert_equals(strategy.highWaterMark, 4);
strategy.highWaterMark = 10;
assert_equals(strategy.highWaterMark, 10);
strategy.highWaterMark = 'banana';
assert_equals(strategy.highWaterMark, 'banana');
}, 'ByteLengthQueuingStrategy\'s highWaterMark property can be set to anything');
done();

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="resources/test-initializer.js"></script>
<script src="count-queuing-strategy.js"></script>
<script>
'use strict';
worker_test('count-queuing-strategy.js');
</script>

View file

@ -0,0 +1,106 @@
'use strict';
if (self.importScripts) {
self.importScripts('/resources/testharness.js');
}
test(() => {
new CountQueuingStrategy({ highWaterMark: 4 });
}, 'Can construct a CountQueuingStrategy with a valid high water mark');
test(() => {
for (const highWaterMark of [-Infinity, NaN, 'foo', {}, () => {}]) {
const strategy = new CountQueuingStrategy({ highWaterMark });
assert_equals(strategy.highWaterMark, highWaterMark, `${highWaterMark} gets set correctly`);
}
}, 'Can construct a CountQueuingStrategy with any value as its high water mark');
test(() => {
const highWaterMark = 1;
const highWaterMarkObjectGetter = {
get highWaterMark() { return highWaterMark; }
};
const error = new Error('wow!');
const highWaterMarkObjectGetterThrowing = {
get highWaterMark() { throw error; }
};
assert_throws({ name: 'TypeError' }, () => new CountQueuingStrategy(), 'construction fails with undefined');
assert_throws({ name: 'TypeError' }, () => new CountQueuingStrategy(null), 'construction fails with null');
assert_throws({ name: 'Error' }, () => new CountQueuingStrategy(highWaterMarkObjectGetterThrowing),
'construction fails with an object with a throwing highWaterMark getter');
// Should not fail:
new CountQueuingStrategy('potato');
new CountQueuingStrategy({});
new CountQueuingStrategy(highWaterMarkObjectGetter);
}, 'CountQueuingStrategy constructor behaves as expected with strange arguments');
test(() => {
const thisValue = null;
const chunk = {
get byteLength() {
throw new TypeError('shouldn\'t be called');
}
};
assert_equals(CountQueuingStrategy.prototype.size.call(thisValue, chunk), 1);
}, 'CountQueuingStrategy.prototype.size should work generically on its this and its arguments');
test(() => {
const size = 1024;
const chunk = { byteLength: size };
const chunkGetter = {
get byteLength() { return size; }
};
const error = new Error('wow!');
const chunkGetterThrowing = {
get byteLength() { throw error; }
};
assert_equals(CountQueuingStrategy.prototype.size(), 1, 'size returns 1 with undefined');
assert_equals(CountQueuingStrategy.prototype.size(null), 1, 'size returns 1 with null');
assert_equals(CountQueuingStrategy.prototype.size('potato'), 1, 'size returns 1 with non-object type');
assert_equals(CountQueuingStrategy.prototype.size({}), 1, 'size returns 1 with empty object');
assert_equals(CountQueuingStrategy.prototype.size(chunk), 1, 'size returns 1 with a chunk');
assert_equals(CountQueuingStrategy.prototype.size(chunkGetter), 1, 'size returns 1 with chunk getter');
assert_equals(CountQueuingStrategy.prototype.size(chunkGetterThrowing), 1,
'size returns 1 with chunk getter that throws');
}, 'CountQueuingStrategy size behaves as expected with strange arguments');
test(() => {
const strategy = new CountQueuingStrategy({ highWaterMark: 4 });
assert_object_equals(Object.getOwnPropertyDescriptor(strategy, 'highWaterMark'),
{ value: 4, writable: true, enumerable: true, configurable: true },
'highWaterMark property should be a data property with the value passed the constructor');
assert_equals(typeof strategy.size, 'function');
}, 'CountQueuingStrategy instances have the correct properties');
test(() => {
const strategy = new CountQueuingStrategy({ highWaterMark: 4 });
assert_equals(strategy.highWaterMark, 4);
strategy.highWaterMark = 10;
assert_equals(strategy.highWaterMark, 10);
strategy.highWaterMark = 'banana';
assert_equals(strategy.highWaterMark, 'banana');
}, 'CountQueuingStrategy\'s highWaterMark property can be set to anything');
done();

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="../resources/test-initializer.js"></script>
<script src="bad-strategies.js"></script>
<script>
'use strict';
worker_test('bad-strategies.js');
</script>

View file

@ -0,0 +1,164 @@
'use strict';
if (self.importScripts) {
self.importScripts('/resources/testharness.js');
}
test(() => {
const theError = new Error('a unique string');
assert_throws(theError, () => {
new ReadableStream({}, {
get size() {
throw theError;
},
highWaterMark: 5
});
}, 'construction should re-throw the error');
}, 'Readable stream: throwing strategy.size getter');
promise_test(t => {
const controllerError = { name: 'controller error' };
const thrownError = { name: 'thrown error' };
let controller;
const rs = new ReadableStream(
{
start(c) {
controller = c;
}
},
{
size() {
controller.error(controllerError);
throw thrownError;
},
highWaterMark: 5
}
);
assert_throws(thrownError, () => controller.enqueue('a'), 'enqueue should re-throw the error');
return promise_rejects(t, controllerError, rs.getReader().closed);
}, 'Readable stream: strategy.size errors the stream and then throws');
promise_test(t => {
const theError = { name: 'my error' };
let controller;
const rs = new ReadableStream(
{
start(c) {
controller = c;
}
},
{
size() {
controller.error(theError);
return Infinity;
},
highWaterMark: 5
}
);
assert_throws(new RangeError(), () => controller.enqueue('a'), 'enqueue should throw a RangeError');
return promise_rejects(t, theError, rs.getReader().closed, 'closed should reject with the error');
}, 'Readable stream: strategy.size errors the stream and then returns Infinity');
promise_test(() => {
const theError = new Error('a unique string');
const rs = new ReadableStream(
{
start(c) {
assert_throws(theError, () => c.enqueue('a'), 'enqueue should throw the error');
}
},
{
size() {
throw theError;
},
highWaterMark: 5
}
);
return rs.getReader().closed.catch(e => {
assert_equals(e, theError, 'closed should reject with the error');
});
}, 'Readable stream: throwing strategy.size method');
test(() => {
const theError = new Error('a unique string');
assert_throws(theError, () => {
new ReadableStream({}, {
size() {
return 1;
},
get highWaterMark() {
throw theError;
}
});
}, 'construction should re-throw the error');
}, 'Readable stream: throwing strategy.highWaterMark getter');
test(() => {
for (const highWaterMark of [-1, -Infinity, NaN, 'foo', {}]) {
assert_throws(new RangeError(), () => {
new ReadableStream({}, {
size() {
return 1;
},
highWaterMark
});
}, 'construction should throw a RangeError for ' + highWaterMark);
}
}, 'Readable stream: invalid strategy.highWaterMark');
promise_test(() => {
const promises = [];
for (const size of [NaN, -Infinity, Infinity, -1]) {
let theError;
const rs = new ReadableStream(
{
start(c) {
try {
c.enqueue('hi');
assert_unreached('enqueue didn\'t throw');
} catch (error) {
assert_equals(error.name, 'RangeError', 'enqueue should throw a RangeError for ' + size);
theError = error;
}
}
},
{
size() {
return size;
},
highWaterMark: 5
}
);
promises.push(rs.getReader().closed.catch(e => {
assert_equals(e, theError, 'closed should reject with the error for ' + size);
}));
}
return Promise.all(promises);
}, 'Readable stream: invalid strategy.size return value');
done();

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="../resources/test-initializer.js"></script>
<script src="bad-underlying-sources.js"></script>
<script>
'use strict';
worker_test('bad-underlying-sources.js');
</script>

View file

@ -0,0 +1,383 @@
'use strict';
if (self.importScripts) {
self.importScripts('/resources/testharness.js');
}
test(() => {
const theError = new Error('a unique string');
assert_throws(theError, () => {
new ReadableStream({
get start() {
throw theError;
}
});
}, 'constructing the stream should re-throw the error');
}, 'Underlying source start: throwing getter');
test(() => {
const theError = new Error('a unique string');
assert_throws(theError, () => {
new ReadableStream({
start() {
throw theError;
}
});
}, 'constructing the stream should re-throw the error');
}, 'Underlying source start: throwing method');
promise_test(t => {
const theError = new Error('a unique string');
const rs = new ReadableStream({
get pull() {
throw theError;
}
});
return promise_rejects(t, theError, rs.getReader().closed);
}, 'Underlying source: throwing pull getter (initial pull)');
promise_test(t => {
const theError = new Error('a unique string');
const rs = new ReadableStream({
pull() {
throw theError;
}
});
return promise_rejects(t, theError, rs.getReader().closed);
}, 'Underlying source: throwing pull method (initial pull)');
promise_test(t => {
const theError = new Error('a unique string');
let counter = 0;
const rs = new ReadableStream({
get pull() {
++counter;
if (counter === 1) {
return c => c.enqueue('a');
}
throw theError;
}
});
const reader = rs.getReader();
return Promise.all([
reader.read().then(r => {
assert_object_equals(r, { value: 'a', done: false }, 'the chunk read should be correct');
}),
promise_rejects(t, theError, reader.closed)
]);
}, 'Underlying source pull: throwing getter (second pull)');
promise_test(t => {
const theError = new Error('a unique string');
let counter = 0;
const rs = new ReadableStream({
pull(c) {
++counter;
if (counter === 1) {
c.enqueue('a');
return;
}
throw theError;
}
});
const reader = rs.getReader();
return Promise.all([
reader.read().then(r => {
assert_object_equals(r, { value: 'a', done: false }, 'the chunk read should be correct');
}),
promise_rejects(t, theError, reader.closed)
]);
}, 'Underlying source pull: throwing method (second pull)');
promise_test(t => {
const theError = new Error('a unique string');
const rs = new ReadableStream({
get cancel() {
throw theError;
}
});
return promise_rejects(t, theError, rs.cancel());
}, 'Underlying source cancel: throwing getter');
promise_test(t => {
const theError = new Error('a unique string');
const rs = new ReadableStream({
cancel() {
throw theError;
}
});
return promise_rejects(t, theError, rs.cancel());
}, 'Underlying source cancel: throwing method');
promise_test(() => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
rs.cancel();
assert_throws(new TypeError, () => controller.enqueue('a'), 'Calling enqueue after canceling should throw');
return rs.getReader().closed;
}, 'Underlying source: calling enqueue on an empty canceled stream should throw');
promise_test(() => {
let controller;
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
c.enqueue('b');
controller = c;
}
});
rs.cancel();
assert_throws(new TypeError, () => controller.enqueue('c'), 'Calling enqueue after canceling should throw');
return rs.getReader().closed;
}, 'Underlying source: calling enqueue on a non-empty canceled stream should throw');
promise_test(() => {
return new ReadableStream({
start(c) {
c.close();
assert_throws(new TypeError(), () => c.enqueue('a'), 'call to enqueue should throw a TypeError');
}
}).getReader().closed;
}, 'Underlying source: calling enqueue on a closed stream should throw');
promise_test(t => {
const theError = new Error('boo');
const closed = new ReadableStream({
start(c) {
c.error(theError);
assert_throws(new TypeError(), () => c.enqueue('a'), 'call to enqueue should throw the error');
}
}).getReader().closed;
return promise_rejects(t, theError, closed);
}, 'Underlying source: calling enqueue on an errored stream should throw');
promise_test(() => {
return new ReadableStream({
start(c) {
c.close();
assert_throws(new TypeError(), () => c.close(), 'second call to close should throw a TypeError');
}
}).getReader().closed;
}, 'Underlying source: calling close twice on an empty stream should throw the second time');
promise_test(() => {
let startCalled = false;
let readCalled = false;
const reader = new ReadableStream({
start(c) {
c.enqueue('a');
c.close();
assert_throws(new TypeError(), () => c.close(), 'second call to close should throw a TypeError');
startCalled = true;
}
}).getReader();
return Promise.all([
reader.read().then(r => {
assert_object_equals(r, { value: 'a', done: false }, 'read() should read the enqueued chunk');
readCalled = true;
}),
reader.closed.then(() => {
assert_true(startCalled);
assert_true(readCalled);
})
]);
}, 'Underlying source: calling close twice on a non-empty stream should throw the second time');
promise_test(() => {
let controller;
let startCalled = false;
const rs = new ReadableStream({
start(c) {
controller = c;
startCalled = true;
}
});
rs.cancel();
assert_throws(new TypeError(), () => controller.close(), 'Calling close after canceling should throw');
return rs.getReader().closed.then(() => {
assert_true(startCalled);
});
}, 'Underlying source: calling close on an empty canceled stream should throw');
promise_test(() => {
let controller;
let startCalled = false;
const rs = new ReadableStream({
start(c) {
controller = c;
c.enqueue('a');
startCalled = true;
}
});
rs.cancel();
assert_throws(new TypeError(), () => controller.close(), 'Calling close after canceling should throw');
return rs.getReader().closed.then(() => {
assert_true(startCalled);
});
}, 'Underlying source: calling close on a non-empty canceled stream should throw');
promise_test(() => {
const theError = new Error('boo');
let startCalled = false;
const closed = new ReadableStream({
start(c) {
c.error(theError);
assert_throws(new TypeError(), () => c.close(), 'call to close should throw a TypeError');
startCalled = true;
}
}).getReader().closed;
return closed.catch(e => {
assert_true(startCalled);
assert_equals(e, theError, 'closed should reject with the error');
});
}, 'Underlying source: calling close after error should throw');
promise_test(() => {
const theError = new Error('boo');
let startCalled = false;
const closed = new ReadableStream({
start(c) {
c.error(theError);
assert_throws(new TypeError(), () => c.error(), 'second call to error should throw a TypeError');
startCalled = true;
}
}).getReader().closed;
return closed.catch(e => {
assert_true(startCalled);
assert_equals(e, theError, 'closed should reject with the error');
});
}, 'Underlying source: calling error twice should throw the second time');
promise_test(() => {
let startCalled = false;
const closed = new ReadableStream({
start(c) {
c.close();
assert_throws(new TypeError(), () => c.error(), 'second call to error should throw a TypeError');
startCalled = true;
}
}).getReader().closed;
return closed.then(() => assert_true(startCalled));
}, 'Underlying source: calling error after close should throw');
promise_test(() => {
let startCalled = false;
const firstError = new Error('1');
const secondError = new Error('2');
const closed = new ReadableStream({
start(c) {
c.error(firstError);
startCalled = true;
return Promise.reject(secondError);
}
}).getReader().closed;
return closed.catch(e => {
assert_true(startCalled);
assert_equals(e, firstError, 'closed should reject with the first error');
});
}, 'Underlying source: calling error and returning a rejected promise from start should cause the stream to error ' +
'with the first error');
promise_test(() => {
let startCalled = false;
const firstError = new Error('1');
const secondError = new Error('2');
const closed = new ReadableStream({
pull(c) {
c.error(firstError);
startCalled = true;
return Promise.reject(secondError);
}
}).getReader().closed;
return closed.catch(e => {
assert_true(startCalled);
assert_equals(e, firstError, 'closed should reject with the first error');
});
}, 'Underlying source: calling error and returning a rejected promise from pull should cause the stream to error ' +
'with the first error');
done();

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="../resources/test-initializer.js"></script>
<script src="../resources/test-utils.js"></script>
<script src="brand-checks.js"></script>
<script>
'use strict';
worker_test('brand-checks.js');
</script>

View file

@ -0,0 +1,151 @@
'use strict';
if (self.importScripts) {
self.importScripts('../resources/test-utils.js');
self.importScripts('/resources/testharness.js');
}
let ReadableStreamReader;
let ReadableStreamController;
test(() => {
// It's not exposed globally, but we test a few of its properties here.
ReadableStreamReader = (new ReadableStream()).getReader().constructor;
}, 'Can get the ReadableStreamReader constructor indirectly');
test(() => {
// It's not exposed globally, but we test a few of its properties here.
new ReadableStream({
start(c) {
ReadableStreamController = c.constructor;
}
});
}, 'Can get the ReadableStreamController constructor indirectly');
function fakeReadableStream() {
return {
cancel() { return Promise.resolve(); },
getReader() { return new ReadableStreamReader(new ReadableStream()); },
pipeThrough(obj) { return obj.readable; },
pipeTo() { return Promise.resolve(); },
tee() { return [realReadableStream(), realReadableStream()]; }
};
}
function realReadableStream() {
return new ReadableStream();
}
function fakeReadableStreamReader() {
return {
get closed() { return Promise.resolve(); },
cancel() { return Promise.resolve(); },
read() { return Promise.resolve({ value: undefined, done: true }); },
releaseLock() { return; }
};
}
function fakeReadableStreamController() {
return {
close() { },
enqueue() { },
error() { }
};
}
promise_test(t => {
return methodRejects(t, ReadableStream.prototype, 'cancel', fakeReadableStream());
}, 'ReadableStream.prototype.cancel enforces a brand check');
test(() => {
methodThrows(ReadableStream.prototype, 'getReader', fakeReadableStream());
}, 'ReadableStream.prototype.getReader enforces a brand check');
test(() => {
methodThrows(ReadableStream.prototype, 'tee', fakeReadableStream());
}, 'ReadableStream.prototype.tee enforces a brand check');
test(() => {
assert_throws(new TypeError(), () => new ReadableStreamReader(fakeReadableStream()),
'Constructing a ReadableStreamReader should throw');
}, 'ReadableStreamReader enforces a brand check on its argument');
promise_test(t => {
return Promise.all([
getterRejects(t, ReadableStreamReader.prototype, 'closed', fakeReadableStreamReader()),
getterRejects(t, ReadableStreamReader.prototype, 'closed', realReadableStream())
]);
}, 'ReadableStreamReader.prototype.closed enforces a brand check');
promise_test(t => {
return Promise.all([
methodRejects(t, ReadableStreamReader.prototype, 'cancel', fakeReadableStreamReader()),
methodRejects(t, ReadableStreamReader.prototype, 'cancel', realReadableStream())
]);
}, 'ReadableStreamReader.prototype.cancel enforces a brand check');
promise_test(t => {
return Promise.all([
methodRejects(t, ReadableStreamReader.prototype, 'read', fakeReadableStreamReader()),
methodRejects(t, ReadableStreamReader.prototype, 'read', realReadableStream())
]);
}, 'ReadableStreamReader.prototype.read enforces a brand check');
test(() => {
methodThrows(ReadableStreamReader.prototype, 'releaseLock', fakeReadableStreamReader());
methodThrows(ReadableStreamReader.prototype, 'releaseLock', realReadableStream());
}, 'ReadableStreamReader.prototype.releaseLock enforces a brand check');
test(() => {
assert_throws(new TypeError(), () => new ReadableStreamController(fakeReadableStream()),
'Constructing a ReadableStreamController should throw');
}, 'ReadableStreamController enforces a brand check on its argument');
test(() => {
assert_throws(new TypeError(), () => new ReadableStreamController(realReadableStream()),
'Constructing a ReadableStreamController should throw');
}, 'ReadableStreamController can\'t be given a fully-constructed ReadableStream');
test(() => {
methodThrows(ReadableStreamController.prototype, 'close', fakeReadableStreamController());
}, 'ReadableStreamController.prototype.close enforces a brand check');
test(() => {
methodThrows(ReadableStreamController.prototype, 'enqueue', fakeReadableStreamController());
}, 'ReadableStreamController.prototype.enqueue enforces a brand check');
test(() => {
methodThrows(ReadableStreamController.prototype, 'error', fakeReadableStreamController());
}, 'ReadableStreamController.prototype.error enforces a brand check');
done();

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="../resources/test-initializer.js"></script>
<script src="../resources/test-utils.js"></script>
<script src="../resources/rs-utils.js"></script>
<script src="cancel.js"></script>
<script>
'use strict';
worker_test('cancel.js');
</script>

View file

@ -0,0 +1,241 @@
'use strict';
if (self.importScripts) {
self.importScripts('../resources/test-utils.js');
self.importScripts('../resources/rs-utils.js');
self.importScripts('/resources/testharness.js');
}
promise_test(() => {
const randomSource = new RandomPushSource();
let cancellationFinished = false;
const rs = new ReadableStream({
start(c) {
randomSource.ondata = c.enqueue.bind(c);
randomSource.onend = c.close.bind(c);
randomSource.onerror = c.error.bind(c);
},
pull() {
randomSource.readStart();
},
cancel() {
randomSource.readStop();
return new Promise(resolve => {
setTimeout(() => {
cancellationFinished = true;
resolve();
}, 1);
});
}
});
const reader = rs.getReader();
// We call delay multiple times to avoid cancelling too early for the
// source to enqueue at least one chunk.
const cancel = delay(5).then(() => delay(5)).then(() => delay(5)).then(() => {
let cancelPromise = reader.cancel();
assert_false(cancellationFinished, 'cancellation in source should happen later');
return cancelPromise;
})
return readableStreamToArray(rs, reader).then(chunks => {
assert_greater_than(chunks.length, 0, 'at least one chunk should be read');
for (let i = 0; i < chunks.length; i++) {
assert_equals(chunks[i].length, 128, 'chunk ' + i + ' should have 128 bytes');
}
return cancel;
}).then(() => {
assert_true(cancellationFinished, 'it returns a promise that is fulfilled when the cancellation finishes');
});
}, 'ReadableStream cancellation: integration test on an infinite stream derived from a random push source');
test(() => {
let recordedReason;
const rs = new ReadableStream({
cancel(reason) {
recordedReason = reason;
}
});
const passedReason = new Error('Sorry, it just wasn\'t meant to be.');
rs.cancel(passedReason);
assert_equals(recordedReason, passedReason,
'the error passed to the underlying source\'s cancel method should equal the one passed to the stream\'s cancel');
}, 'ReadableStream cancellation: cancel(reason) should pass through the given reason to the underlying source');
promise_test(() => {
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
c.close();
},
cancel() {
assert_unreached('underlying source cancel() should not have been called');
}
});
const reader = rs.getReader();
return rs.cancel().then(() => {
assert_unreached('cancel() should be rejected');
}, e => {
assert_equals(e.name, 'TypeError', 'cancel() should be rejected with a TypeError');
}).then(() => {
return reader.read();
}).then(result => {
assert_object_equals(result, { value: 'a', done: false }, 'read() should still work after the attempted cancel');
return reader.closed;
});
}, 'ReadableStream cancellation: cancel() on a locked stream should fail and not call the underlying source cancel');
promise_test(() => {
let cancelReceived = false;
const cancelReason = new Error('I am tired of this stream, I prefer to cancel it');
const rs = new ReadableStream({
cancel(reason) {
cancelReceived = true;
assert_equals(reason, cancelReason, 'cancellation reason given to the underlying source should be equal to the one passed');
}
});
return rs.cancel(cancelReason).then(() => {
assert_true(cancelReceived);
});
}, 'ReadableStream cancellation: should fulfill promise when cancel callback went fine');
promise_test(() => {
const rs = new ReadableStream({
cancel() {
return 'Hello';
}
});
return rs.cancel().then(v => {
assert_equals(v, undefined, 'cancel() return value should be fulfilled with undefined');
});
}, 'ReadableStream cancellation: returning a value from the underlying source\'s cancel should not affect the fulfillment value of the promise returned by the stream\'s cancel');
promise_test(() => {
const thrownError = new Error('test');
let cancelCalled = false;
const rs = new ReadableStream({
cancel() {
cancelCalled = true;
throw thrownError;
}
});
return rs.cancel('test').then(() => {
assert_unreached('cancel should reject');
}, e => {
assert_true(cancelCalled);
assert_equals(e, thrownError);
});
}, 'ReadableStream cancellation: should reject promise when cancel callback raises an exception');
promise_test(() => {
const cancelReason = new Error('test');
const rs = new ReadableStream({
cancel(error) {
assert_equals(error, cancelReason);
return delay(1);
}
});
return rs.cancel(cancelReason);
}, 'ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should fulfill when that one does (1)');
promise_test(() => {
let resolveSourceCancelPromise;
let sourceCancelPromiseHasFulfilled = false;
const rs = new ReadableStream({
cancel() {
const sourceCancelPromise = new Promise(resolve => resolveSourceCancelPromise = resolve);
sourceCancelPromise.then(() => {
sourceCancelPromiseHasFulfilled = true;
});
return sourceCancelPromise;
}
});
setTimeout(() => resolveSourceCancelPromise('Hello'), 1);
return rs.cancel().then(value => {
assert_true(sourceCancelPromiseHasFulfilled, 'cancel() return value should be fulfilled only after the promise returned by the underlying source\'s cancel');
assert_equals(value, undefined, 'cancel() return value should be fulfilled with undefined');
});
}, 'ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should fulfill when that one does (2)');
promise_test(() => {
let rejectSourceCancelPromise;
let sourceCancelPromiseHasRejected = false;
const rs = new ReadableStream({
cancel() {
const sourceCancelPromise = new Promise((resolve, reject) => rejectSourceCancelPromise = reject);
sourceCancelPromise.catch(() => {
sourceCancelPromiseHasRejected = true;
});
return sourceCancelPromise;
}
});
const errorInCancel = new Error('Sorry, it just wasn\'t meant to be.');
setTimeout(() => rejectSourceCancelPromise(errorInCancel), 1);
return rs.cancel().then(() => {
assert_unreached('cancel() return value should be rejected');
}, r => {
assert_true(sourceCancelPromiseHasRejected, 'cancel() return value should be rejected only after the promise returned by the underlying source\'s cancel');
assert_equals(r, errorInCancel, 'cancel() return value should be rejected with the underlying source\'s rejection reason');
});
}, 'ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should reject when that one does');
promise_test(() => {
const rs = new ReadableStream({
start() {
return new Promise(() => {});
},
pull() {
assert_unreached('pull should not have been called');
}
});
return Promise.all([rs.cancel(), rs.getReader().closed]);
}, 'ReadableStream cancellation: cancelling before start finishes should prevent pull() from being called');
done();

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="../resources/test-initializer.js"></script>
<script src="count-queuing-strategy-integration.js"></script>
<script>
'use strict';
worker_test('count-queuing-strategy-integration.js');
</script>

View file

@ -0,0 +1,213 @@
'use strict';
if (self.importScripts) {
self.importScripts('/resources/testharness.js');
}
test(() => {
new ReadableStream({}, new CountQueuingStrategy({ highWaterMark: 4 }));
}, 'Can construct a readable stream with a valid CountQueuingStrategy');
promise_test(() => {
let controller;
const rs = new ReadableStream(
{
start(c) {
controller = c;
}
},
new CountQueuingStrategy({ highWaterMark: 0 })
);
const reader = rs.getReader();
assert_equals(controller.desiredSize, 0, '0 reads, 0 enqueues: desiredSize should be 0');
controller.enqueue('a');
assert_equals(controller.desiredSize, -1, '0 reads, 1 enqueue: desiredSize should be -1');
controller.enqueue('b');
assert_equals(controller.desiredSize, -2, '0 reads, 2 enqueues: desiredSize should be -2');
controller.enqueue('c');
assert_equals(controller.desiredSize, -3, '0 reads, 3 enqueues: desiredSize should be -3');
controller.enqueue('d');
assert_equals(controller.desiredSize, -4, '0 reads, 4 enqueues: desiredSize should be -4');
return reader.read()
.then(result => {
assert_object_equals(result, { value: 'a', done: false },
'1st read gives back the 1st chunk enqueued (queue now contains 3 chunks)');
return reader.read();
})
.then(result => {
assert_object_equals(result, { value: 'b', done: false },
'2nd read gives back the 2nd chunk enqueued (queue now contains 2 chunks)');
return reader.read();
})
.then(result => {
assert_object_equals(result, { value: 'c', done: false },
'3rd read gives back the 3rd chunk enqueued (queue now contains 1 chunk)');
assert_equals(controller.desiredSize, -1, '3 reads, 4 enqueues: desiredSize should be -1');
controller.enqueue('e');
assert_equals(controller.desiredSize, -2, '3 reads, 5 enqueues: desiredSize should be -2');
return reader.read();
})
.then(result => {
assert_object_equals(result, { value: 'd', done: false },
'4th read gives back the 4th chunk enqueued (queue now contains 1 chunks)');
return reader.read();
}).then(result => {
assert_object_equals(result, { value: 'e', done: false },
'5th read gives back the 5th chunk enqueued (queue now contains 0 chunks)');
assert_equals(controller.desiredSize, 0, '5 reads, 5 enqueues: desiredSize should be 0');
controller.enqueue('f');
assert_equals(controller.desiredSize, -1, '5 reads, 6 enqueues: desiredSize should be -1');
controller.enqueue('g');
assert_equals(controller.desiredSize, -2, '5 reads, 7 enqueues: desiredSize should be -2');
});
}, 'Correctly governs a ReadableStreamController\'s desiredSize property (HWM = 0)');
promise_test(() => {
let controller;
const rs = new ReadableStream(
{
start(c) {
controller = c;
}
},
new CountQueuingStrategy({ highWaterMark: 1 })
);
const reader = rs.getReader();
assert_equals(controller.desiredSize, 1, '0 reads, 0 enqueues: desiredSize should be 1');
controller.enqueue('a');
assert_equals(controller.desiredSize, 0, '0 reads, 1 enqueue: desiredSize should be 0');
controller.enqueue('b');
assert_equals(controller.desiredSize, -1, '0 reads, 2 enqueues: desiredSize should be -1');
controller.enqueue('c');
assert_equals(controller.desiredSize, -2, '0 reads, 3 enqueues: desiredSize should be -2');
controller.enqueue('d');
assert_equals(controller.desiredSize, -3, '0 reads, 4 enqueues: desiredSize should be -3');
return reader.read()
.then(result => {
assert_object_equals(result, { value: 'a', done: false },
'1st read gives back the 1st chunk enqueued (queue now contains 3 chunks)');
return reader.read();
})
.then(result => {
assert_object_equals(result, { value: 'b', done: false },
'2nd read gives back the 2nd chunk enqueued (queue now contains 2 chunks)');
return reader.read();
})
.then(result => {
assert_object_equals(result, { value: 'c', done: false },
'3rd read gives back the 3rd chunk enqueued (queue now contains 1 chunk)');
assert_equals(controller.desiredSize, 0, '3 reads, 4 enqueues: desiredSize should be 0');
controller.enqueue('e');
assert_equals(controller.desiredSize, -1, '3 reads, 5 enqueues: desiredSize should be -1');
return reader.read();
})
.then(result => {
assert_object_equals(result, { value: 'd', done: false },
'4th read gives back the 4th chunk enqueued (queue now contains 1 chunks)');
return reader.read();
})
.then(result => {
assert_object_equals(result, { value: 'e', done: false },
'5th read gives back the 5th chunk enqueued (queue now contains 0 chunks)');
assert_equals(controller.desiredSize, 1, '5 reads, 5 enqueues: desiredSize should be 1');
controller.enqueue('f');
assert_equals(controller.desiredSize, 0, '5 reads, 6 enqueues: desiredSize should be 0');
controller.enqueue('g');
assert_equals(controller.desiredSize, -1, '5 reads, 7 enqueues: desiredSize should be -1');
});
}, 'Correctly governs a ReadableStreamController\'s desiredSize property (HWM = 1)');
promise_test(() => {
let controller;
const rs = new ReadableStream(
{
start(c) {
controller = c;
}
},
new CountQueuingStrategy({ highWaterMark: 4 })
);
const reader = rs.getReader();
assert_equals(controller.desiredSize, 4, '0 reads, 0 enqueues: desiredSize should be 4');
controller.enqueue('a');
assert_equals(controller.desiredSize, 3, '0 reads, 1 enqueue: desiredSize should be 3');
controller.enqueue('b');
assert_equals(controller.desiredSize, 2, '0 reads, 2 enqueues: desiredSize should be 2');
controller.enqueue('c');
assert_equals(controller.desiredSize, 1, '0 reads, 3 enqueues: desiredSize should be 1');
controller.enqueue('d');
assert_equals(controller.desiredSize, 0, '0 reads, 4 enqueues: desiredSize should be 0');
controller.enqueue('e');
assert_equals(controller.desiredSize, -1, '0 reads, 5 enqueues: desiredSize should be -1');
controller.enqueue('f');
assert_equals(controller.desiredSize, -2, '0 reads, 6 enqueues: desiredSize should be -2');
reader.read()
.then(result => {
assert_object_equals(result, { value: 'a', done: false },
'1st read gives back the 1st chunk enqueued (queue now contains 5 chunks)');
return reader.read();
})
.then(result => {
assert_object_equals(result, { value: 'b', done: false },
'2nd read gives back the 2nd chunk enqueued (queue now contains 4 chunks)');
assert_equals(controller.desiredSize, 0, '2 reads, 6 enqueues: desiredSize should be 0');
controller.enqueue('g');
assert_equals(controller.desiredSize, -1, '2 reads, 7 enqueues: desiredSize should be -1');
return reader.read();
})
.then(result => {
assert_object_equals(result, { value: 'c', done: false },
'3rd read gives back the 3rd chunk enqueued (queue now contains 4 chunks)');
return reader.read();
})
.then(result => {
assert_object_equals(result, { value: 'd', done: false },
'4th read gives back the 4th chunk enqueued (queue now contains 3 chunks)');
return reader.read();
})
.then(result => {
assert_object_equals(result, { value: 'e', done: false },
'5th read gives back the 5th chunk enqueued (queue now contains 2 chunks)');
return reader.read();
})
.then(result => {
assert_object_equals(result, { value: 'f', done: false },
'6th read gives back the 6th chunk enqueued (queue now contains 0 chunks)');
assert_equals(controller.desiredSize, 3, '6 reads, 7 enqueues: desiredSize should be 3');
controller.enqueue('h');
assert_equals(controller.desiredSize, 2, '6 reads, 8 enqueues: desiredSize should be 2');
controller.enqueue('i');
assert_equals(controller.desiredSize, 1, '6 reads, 9 enqueues: desiredSize should be 1');
controller.enqueue('j');
assert_equals(controller.desiredSize, 0, '6 reads, 10 enqueues: desiredSize should be 0');
controller.enqueue('k');
assert_equals(controller.desiredSize, -1, '6 reads, 11 enqueues: desiredSize should be -1');
});
}, 'Correctly governs a ReadableStreamController\'s desiredSize property (HWM = 4)');
done();

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="../resources/test-initializer.js"></script>
<script src="../resources/test-utils.js"></script>
<script src="garbage-collection.js"></script>
<script>
'use strict';
worker_test('garbage-collection.js');
</script>

View file

@ -0,0 +1,75 @@
'use strict';
if (self.importScripts) {
self.importScripts('../resources/test-utils.js');
self.importScripts('/resources/testharness.js');
}
promise_test(() => {
let controller;
new ReadableStream({
start(c) {
controller = c;
}
});
garbageCollect();
return delay(50).then(() => {
controller.close();
assert_throws(new TypeError(), () => controller.close(), 'close should throw a TypeError the second time');
assert_throws(new TypeError(), () => controller.error(), 'error should throw a TypeError on a closed stream');
});
}, 'ReadableStreamController methods should continue working properly when scripts lose their reference to the ' +
'readable stream');
promise_test(() => {
let controller;
const closedPromise = new ReadableStream({
start(c) {
controller = c;
}
}).getReader().closed;
garbageCollect();
return delay(50).then(() => controller.close()).then(() => closedPromise);
}, 'ReadableStream closed promise should fulfill even if the stream and reader JS references are lost');
promise_test(t => {
const theError = new Error('boo');
let controller;
const closedPromise = new ReadableStream({
start(c) {
controller = c;
}
}).getReader().closed;
garbageCollect();
return delay(50).then(() => controller.error(theError))
.then(() => promise_rejects(t, theError, closedPromise));
}, 'ReadableStream closed promise should reject even if stream and reader JS references are lost');
promise_test(() => {
const rs = new ReadableStream({});
rs.getReader();
garbageCollect();
return delay(50).then(() => assert_throws(new TypeError(), () => rs.getReader(),
'old reader should still be locking the stream even after garbage collection'));
}, 'Garbage-collecting a ReadableStreamReader should not unlock its stream');
done();

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="../resources/test-initializer.js"></script>
<script src="../resources/test-utils.js"></script>
<script src="../resources/rs-utils.js"></script>
<script src="general.js"></script>
<script>
'use strict';
worker_test('general.js');
</script>

View file

@ -0,0 +1,859 @@
'use strict';
if (self.importScripts) {
self.importScripts('../resources/test-utils.js');
self.importScripts('../resources/rs-utils.js');
self.importScripts('/resources/testharness.js');
}
test(() => {
new ReadableStream(); // ReadableStream constructed with no parameters
new ReadableStream({ }); // ReadableStream constructed with an empty object as parameter
new ReadableStream({ type: undefined }); // ReadableStream constructed with undefined type
new ReadableStream(undefined); // ReadableStream constructed with undefined as parameter
let x;
new ReadableStream(x); // ReadableStream constructed with an undefined variable as parameter
}, 'ReadableStream can be constructed with no errors');
test(() => {
assert_throws(new TypeError(), () => new ReadableStream(null), 'constructor should throw when the source is null');
}, 'ReadableStream can\'t be constructed with garbage');
test(() => {
assert_throws(new RangeError(), () => new ReadableStream({ type: null }),
'constructor should throw when the type is null');
assert_throws(new RangeError(), () => new ReadableStream({ type: '' }),
'constructor should throw when the type is empty string');
assert_throws(new RangeError(), () => new ReadableStream({ type: 'asdf' }),
'constructor should throw when the type is asdf');
}, 'ReadableStream can\'t be constructed with an invalid type');
test(() => {
const methods = ['cancel', 'constructor', 'getReader', 'pipeThrough', 'pipeTo', 'tee'];
const properties = methods.concat(['locked']).sort();
const rs = new ReadableStream();
const proto = Object.getPrototypeOf(rs);
assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties, 'should have all the correct methods');
for (const m of methods) {
const propDesc = Object.getOwnPropertyDescriptor(proto, m);
assert_false(propDesc.enumerable, 'method should be non-enumerable');
assert_true(propDesc.configurable, 'method should be configurable');
assert_true(propDesc.writable, 'method should be writable');
assert_equals(typeof rs[m], 'function', 'method should be a function');
}
const lockedPropDesc = Object.getOwnPropertyDescriptor(proto, 'locked');
assert_false(lockedPropDesc.enumerable, 'locked should be non-enumerable');
assert_equals(lockedPropDesc.writable, undefined, 'locked should not be a data property');
assert_equals(typeof lockedPropDesc.get, 'function', 'locked should have a getter');
assert_equals(lockedPropDesc.set, undefined, 'locked should not have a setter');
assert_true(lockedPropDesc.configurable, 'locked should be configurable');
assert_equals(rs.cancel.length, 1, 'cancel should have 1 parameter');
assert_equals(rs.constructor.length, 0, 'constructor should have no parameters');
assert_equals(rs.getReader.length, 0, 'getReader should have no parameters');
assert_equals(rs.pipeThrough.length, 2, 'pipeThrough should have 2 parameters');
assert_equals(rs.pipeTo.length, 1, 'pipeTo should have 1 parameter');
assert_equals(rs.tee.length, 0, 'tee should have no parameters');
}, 'ReadableStream instances should have the correct list of properties');
test(() => {
assert_throws(new TypeError(), () => {
new ReadableStream({ start: 'potato' });
}, 'constructor should throw when start is not a function');
}, 'ReadableStream constructor should throw for non-function start arguments');
test(() => {
new ReadableStream({ cancel: '2' });
}, 'ReadableStream constructor can get initial garbage as cancel argument');
test(() => {
new ReadableStream({ pull: { } });
}, 'ReadableStream constructor can get initial garbage as pull argument');
test(() => {
let startCalled = false;
const source = {
start(controller) {
assert_equals(this, source, 'source is this during start');
const methods = ['close', 'enqueue', 'error', 'constructor'];
const properties = ['desiredSize'].concat(methods).sort();
const proto = Object.getPrototypeOf(controller);
assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties,
'the controller should have the right properties');
for (const m of methods) {
const propDesc = Object.getOwnPropertyDescriptor(proto, m);
assert_equals(typeof controller[m], 'function', `should have a ${m} method`);
assert_false(propDesc.enumerable, m + ' should be non-enumerable');
assert_true(propDesc.configurable, m + ' should be configurable');
assert_true(propDesc.writable, m + ' should be writable');
}
const desiredSizePropDesc = Object.getOwnPropertyDescriptor(proto, 'desiredSize');
assert_false(desiredSizePropDesc.enumerable, 'desiredSize should be non-enumerable');
assert_equals(desiredSizePropDesc.writable, undefined, 'desiredSize should not be a data property');
assert_equals(typeof desiredSizePropDesc.get, 'function', 'desiredSize should have a getter');
assert_equals(desiredSizePropDesc.set, undefined, 'desiredSize should not have a setter');
assert_true(desiredSizePropDesc.configurable, 'desiredSize should be configurable');
assert_equals(controller.close.length, 0, 'close should have no parameters');
assert_equals(controller.constructor.length, 4, 'constructor should have 4 parameter');
assert_equals(controller.enqueue.length, 1, 'enqueue should have 1 parameter');
assert_equals(controller.error.length, 1, 'error should have 1 parameter');
startCalled = true;
}
};
new ReadableStream(source);
assert_true(startCalled);
}, 'ReadableStream start should be called with the proper parameters');
test(() => {
let startCalled = false;
const source = {
start(controller) {
const properties = ['close', 'constructor', 'desiredSize', 'enqueue', 'error'];
assert_array_equals(Object.getOwnPropertyNames(Object.getPrototypeOf(controller)).sort(), properties,
'prototype should have the right properties');
controller.test = '';
assert_array_equals(Object.getOwnPropertyNames(Object.getPrototypeOf(controller)).sort(), properties,
'prototype should still have the right properties');
assert_not_equals(Object.getOwnPropertyNames(controller).indexOf('test'), -1,
'"test" should be a property of the controller');
startCalled = true;
}
};
new ReadableStream(source);
assert_true(startCalled);
}, 'ReadableStream start controller parameter should be extensible');
promise_test(() => {
function SimpleStreamSource() {}
let resolve;
const promise = new Promise(r => resolve = r);
SimpleStreamSource.prototype = {
start: resolve
};
new ReadableStream(new SimpleStreamSource());
return promise;
}, 'ReadableStream should be able to call start method within prototype chain of its source');
promise_test(() => {
const rs = new ReadableStream({
start(c) {
return delay(5).then(() => {
c.enqueue('a');
c.close();
});
}
});
const reader = rs.getReader();
return reader.read().then(r => {
assert_object_equals(r, { value: 'a', done: false }, 'value read should be the one enqueued');
return reader.closed;
});
}, 'ReadableStream start should be able to return a promise');
promise_test(() => {
const theError = new Error('rejected!');
const rs = new ReadableStream({
start() {
return delay(1).then(() => { throw theError; });
}
});
return rs.getReader().closed.then(() => {
assert_unreached('closed promise should be rejected');
}, e => {
assert_equals(e, theError, 'promise should be rejected with the same error');
});
}, 'ReadableStream start should be able to return a promise and reject it');
promise_test(() => {
const objects = [
{ potato: 'Give me more!' },
'test',
1
];
const rs = new ReadableStream({
start(c) {
for (const o of objects) {
c.enqueue(o);
}
c.close();
}
});
const reader = rs.getReader();
return Promise.all([reader.read(), reader.read(), reader.read(), reader.closed]).then(r => {
assert_object_equals(r[0], { value: objects[0], done: false }, 'value read should be the one enqueued');
assert_object_equals(r[1], { value: objects[1], done: false }, 'value read should be the one enqueued');
assert_object_equals(r[2], { value: objects[2], done: false }, 'value read should be the one enqueued');
});
}, 'ReadableStream should be able to enqueue different objects.');
promise_test(() => {
const error = new Error('pull failure');
const rs = new ReadableStream({
pull() {
return Promise.reject(error);
}
});
const reader = rs.getReader();
let closed = false;
let read = false;
return Promise.all([
reader.closed.then(() => {
assert_unreached('closed should be rejected');
}, e => {
closed = true;
assert_true(read);
assert_equals(e, error, 'closed should be rejected with the thrown error');
}),
reader.read().then(() => {
assert_unreached('read() should be rejected');
}, e => {
read = true;
assert_false(closed);
assert_equals(e, error, 'read() should be rejected with the thrown error');
})
]);
}, 'ReadableStream: if pull rejects, it should error the stream');
promise_test(() => {
let pullCount = 0;
const startPromise = Promise.resolve();
new ReadableStream({
start() {
return startPromise;
},
pull() {
pullCount++;
}
});
return startPromise.then(() => {
assert_equals(pullCount, 1, 'pull should be called once start finishes');
return delay(10);
}).then(() => {
assert_equals(pullCount, 1, 'pull should be called exactly once');
});
}, 'ReadableStream: should only call pull once upon starting the stream');
promise_test(() => {
let pullCount = 0;
const startPromise = Promise.resolve();
const rs = new ReadableStream({
start() {
return startPromise;
},
pull(c) {
// Don't enqueue immediately after start. We want the stream to be empty when we call .read() on it.
if (pullCount > 0) {
c.enqueue(pullCount);
}
++pullCount;
}
});
return startPromise.then(() => {
assert_equals(pullCount, 1, 'pull should be called once start finishes');
}).then(() => {
const reader = rs.getReader();
const read = reader.read();
assert_equals(pullCount, 2, 'pull should be called when read is called');
return read;
}).then(result => {
assert_equals(pullCount, 3, 'pull should be called again in reaction to calling read');
assert_object_equals(result, { value: 1, done: false }, 'the result read should be the one enqueued');
});
}, 'ReadableStream: should call pull when trying to read from a started, empty stream');
promise_test(() => {
let pullCount = 0;
const startPromise = Promise.resolve();
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
return startPromise;
},
pull() {
pullCount++;
}
});
const read = rs.getReader().read();
assert_equals(pullCount, 0, 'calling read() should not cause pull to be called yet');
return startPromise.then(() => {
assert_equals(pullCount, 1, 'pull should be called once start finishes');
return read;
}).then(r => {
assert_object_equals(r, { value: 'a', done: false }, 'first read() should return first chunk');
assert_equals(pullCount, 1, 'pull should not have been called again');
return delay(10);
}).then(() => {
assert_equals(pullCount, 1, 'pull should be called exactly once');
});
}, 'ReadableStream: should only call pull once on a non-empty stream read from before start fulfills');
promise_test(() => {
let pullCount = 0;
const startPromise = Promise.resolve();
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
return startPromise;
},
pull() {
pullCount++;
}
});
return startPromise.then(() => {
assert_equals(pullCount, 0, 'pull should not be called once start finishes, since the queue is full');
const read = rs.getReader().read();
assert_equals(pullCount, 1, 'calling read() should cause pull to be called immediately');
return read;
}).then(r => {
assert_object_equals(r, { value: 'a', done: false }, 'first read() should return first chunk');
return delay(10);
}).then(() => {
assert_equals(pullCount, 1, 'pull should be called exactly once');
});
}, 'ReadableStream: should only call pull once on a non-empty stream read from after start fulfills');
promise_test(() => {
let pullCount = 0;
let controller;
const startPromise = Promise.resolve();
const rs = new ReadableStream({
start(c) {
controller = c;
return startPromise;
},
pull() {
++pullCount;
}
});
const reader = rs.getReader();
return startPromise.then(() => {
assert_equals(pullCount, 1, 'pull should have been called once by the time the stream starts');
controller.enqueue('a');
assert_equals(pullCount, 1, 'pull should not have been called again after enqueue');
return reader.read();
}).then(() => {
assert_equals(pullCount, 2, 'pull should have been called again after read');
return delay(10);
}).then(() => {
assert_equals(pullCount, 2, 'pull should be called exactly twice');
});
}, 'ReadableStream: should call pull in reaction to read()ing the last chunk, if not draining');
promise_test(() => {
let pullCount = 0;
let controller;
const startPromise = Promise.resolve();
const rs = new ReadableStream({
start(c) {
controller = c;
return startPromise;
},
pull() {
++pullCount;
}
});
const reader = rs.getReader();
return startPromise.then(() => {
assert_equals(pullCount, 1, 'pull should have been called once by the time the stream starts');
controller.enqueue('a');
assert_equals(pullCount, 1, 'pull should not have been called again after enqueue');
controller.close();
return reader.read();
}).then(() => {
assert_equals(pullCount, 1, 'pull should not have been called a second time after read');
return delay(10);
}).then(() => {
assert_equals(pullCount, 1, 'pull should be called exactly once');
});
}, 'ReadableStream: should not call pull() in reaction to read()ing the last chunk, if draining');
promise_test(() => {
let resolve;
let returnedPromise;
let timesCalled = 0;
const startPromise = Promise.resolve();
const rs = new ReadableStream({
start() {
return startPromise;
},
pull(c) {
c.enqueue(++timesCalled);
returnedPromise = new Promise(r => resolve = r);
return returnedPromise;
}
});
const reader = rs.getReader();
return startPromise.then(() => {
return reader.read();
}).then(result1 => {
assert_equals(timesCalled, 1,
'pull should have been called once after start, but not yet have been called a second time');
assert_object_equals(result1, { value: 1, done: false }, 'read() should fulfill with the enqueued value');
return delay(10);
}).then(() => {
assert_equals(timesCalled, 1, 'after 10 ms, pull should still only have been called once');
resolve();
return returnedPromise;
}).then(() => {
assert_equals(timesCalled, 2,
'after the promise returned by pull is fulfilled, pull should be called a second time');
});
}, 'ReadableStream: should not call pull until the previous pull call\'s promise fulfills');
promise_test(() => {
let timesCalled = 0;
const startPromise = Promise.resolve();
const rs = new ReadableStream(
{
start(c) {
c.enqueue('a');
c.enqueue('b');
c.enqueue('c');
return startPromise;
},
pull() {
++timesCalled;
}
},
{
size() {
return 1;
},
highWaterMark: Infinity
}
);
const reader = rs.getReader();
return startPromise.then(() => {
return reader.read();
}).then(result1 => {
assert_object_equals(result1, { value: 'a', done: false }, 'first chunk should be as expected');
return reader.read();
}).then(result2 => {
assert_object_equals(result2, { value: 'b', done: false }, 'second chunk should be as expected');
return reader.read();
}).then(result3 => {
assert_object_equals(result3, { value: 'c', done: false }, 'third chunk should be as expected');
return delay(10);
}).then(() => {
// Once for after start, and once for every read.
assert_equals(timesCalled, 4, 'pull() should be called exactly four times');
});
}, 'ReadableStream: should pull after start, and after every read');
promise_test(() => {
let timesCalled = 0;
const startPromise = Promise.resolve();
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
c.close();
return startPromise;
},
pull() {
++timesCalled;
}
});
const reader = rs.getReader();
return startPromise.then(() => {
assert_equals(timesCalled, 0, 'after start finishes, pull should not have been called');
return reader.read();
}).then(() => {
assert_equals(timesCalled, 0, 'reading should not have triggered a pull call');
return reader.closed;
}).then(() => {
assert_equals(timesCalled, 0, 'stream should have closed with still no calls to pull');
});
}, 'ReadableStream: should not call pull after start if the stream is now closed');
promise_test(() => {
let timesCalled = 0;
let resolve;
const ready = new Promise(r => resolve = r);
new ReadableStream(
{
start() {},
pull(c) {
c.enqueue(++timesCalled);
if (timesCalled === 4) {
resolve();
}
}
},
{
size() {
return 1;
},
highWaterMark: 4
}
);
return ready.then(() => {
// after start: size = 0, pull()
// after enqueue(1): size = 1, pull()
// after enqueue(2): size = 2, pull()
// after enqueue(3): size = 3, pull()
// after enqueue(4): size = 4, do not pull
assert_equals(timesCalled, 4, 'pull() should have been called four times');
});
}, 'ReadableStream: should call pull after enqueueing from inside pull (with no read requests), if strategy allows');
promise_test(() => {
let pullCalled = false;
const rs = new ReadableStream({
pull(c) {
pullCalled = true;
c.close();
}
});
const reader = rs.getReader();
return reader.closed.then(() => {
assert_true(pullCalled);
});
}, 'ReadableStream pull should be able to close a stream.');
promise_test(t => {
const controllerError = { name: 'controller error' };
const rs = new ReadableStream({
pull(c) {
c.error(controllerError);
}
});
return promise_rejects(t, controllerError, rs.getReader().closed);
}, 'ReadableStream pull should be able to error a stream.');
promise_test(t => {
const controllerError = { name: 'controller error' };
const thrownError = { name: 'thrown error' };
const rs = new ReadableStream({
pull(c) {
c.error(controllerError);
throw thrownError;
}
});
return promise_rejects(t, controllerError, rs.getReader().closed);
}, 'ReadableStream pull should be able to error a stream and throw.');
test(() => {
let startCalled = false;
new ReadableStream({
start(c) {
assert_equals(c.enqueue('a'), undefined, 'the first enqueue should return undefined');
c.close();
assert_throws(new TypeError(), () => c.enqueue('b'), 'enqueue after close should throw a TypeError');
startCalled = true;
}
});
assert_true(startCalled);
}, 'ReadableStream: enqueue should throw when the stream is readable but draining');
test(() => {
let startCalled = false;
new ReadableStream({
start(c) {
c.close();
assert_throws(new TypeError(), () => c.enqueue('a'), 'enqueue after close should throw a TypeError');
startCalled = true;
}
});
assert_true(startCalled);
}, 'ReadableStream: enqueue should throw when the stream is closed');
promise_test(() => {
let startCalled = 0;
let pullCalled = 0;
let cancelCalled = 0;
/* eslint-disable no-use-before-define */
class Source {
start(c) {
startCalled++;
assert_equals(this, theSource, 'start() should be called with the correct this');
c.enqueue('a');
}
pull() {
pullCalled++;
assert_equals(this, theSource, 'pull() should be called with the correct this');
}
cancel() {
cancelCalled++;
assert_equals(this, theSource, 'cancel() should be called with the correct this');
}
}
/* eslint-enable no-use-before-define */
const theSource = new Source();
theSource.debugName = 'the source object passed to the constructor'; // makes test failures easier to diagnose
const rs = new ReadableStream(theSource);
const reader = rs.getReader();
return reader.read().then(() => {
reader.releaseLock();
rs.cancel();
assert_equals(startCalled, 1);
assert_equals(pullCalled, 1);
assert_equals(cancelCalled, 1);
return rs.getReader().closed;
});
}, 'ReadableStream: should call underlying source methods as methods');
test(() => {
let startCalled = false;
new ReadableStream({
start(c) {
assert_equals(c.desiredSize, 1);
c.enqueue('a');
assert_equals(c.desiredSize, 0);
c.enqueue('b');
assert_equals(c.desiredSize, -1);
c.enqueue('c');
assert_equals(c.desiredSize, -2);
c.enqueue('d');
assert_equals(c.desiredSize, -3);
c.enqueue('e');
startCalled = true;
}
});
assert_true(startCalled);
}, 'ReadableStream strategies: the default strategy should give desiredSize of 1 to start, decreasing by 1 per enqueue');
promise_test(() => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
const reader = rs.getReader();
assert_equals(controller.desiredSize, 1, 'desiredSize should start at 1');
controller.enqueue('a');
assert_equals(controller.desiredSize, 0, 'desiredSize should decrease to 0 after first enqueue');
return reader.read().then(result1 => {
assert_object_equals(result1, { value: 'a', done: false }, 'first chunk read should be correct');
assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 after the first read');
controller.enqueue('b');
assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 after the second enqueue');
return reader.read();
}).then(result2 => {
assert_object_equals(result2, { value: 'b', done: false }, 'second chunk read should be correct');
assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 after the second read');
controller.enqueue('c');
assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 after the third enqueue');
return reader.read();
}).then(result3 => {
assert_object_equals(result3, { value: 'c', done: false }, 'third chunk read should be correct');
assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 after the third read');
controller.enqueue('d');
assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 after the fourth enqueue');
});
}, 'ReadableStream strategies: the default strategy should continue giving desiredSize of 1 if the chunks are read immediately');
promise_test(t => {
const randomSource = new RandomPushSource(8);
const rs = new ReadableStream({
start(c) {
assert_equals(typeof c, 'object', 'c should be an object in start');
assert_equals(typeof c.enqueue, 'function', 'enqueue should be a function in start');
assert_equals(typeof c.close, 'function', 'close should be a function in start');
assert_equals(typeof c.error, 'function', 'error should be a function in start');
randomSource.ondata = t.step_func(chunk => {
if (!c.enqueue(chunk) <= 0) {
randomSource.readStop();
}
});
randomSource.onend = c.close.bind(c);
randomSource.onerror = c.error.bind(c);
},
pull(c) {
assert_equals(typeof c, 'object', 'c should be an object in pull');
assert_equals(typeof c.enqueue, 'function', 'enqueue should be a function in pull');
assert_equals(typeof c.close, 'function', 'close should be a function in pull');
randomSource.readStart();
}
});
return readableStreamToArray(rs).then(chunks => {
assert_equals(chunks.length, 8, '8 chunks should be read');
for (const chunk of chunks) {
assert_equals(chunk.length, 128, 'chunk should have 128 bytes');
}
});
}, 'ReadableStream integration test: adapting a random push source');
promise_test(() => {
const rs = sequentialReadableStream(10);
return readableStreamToArray(rs).then(chunks => {
assert_true(rs.source.closed, 'source should be closed after all chunks are read');
assert_array_equals(chunks, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'the expected 10 chunks should be read');
});
}, 'ReadableStream integration test: adapting a sync pull source');
promise_test(() => {
const rs = sequentialReadableStream(10, { async: true });
return readableStreamToArray(rs).then(chunks => {
assert_true(rs.source.closed, 'source should be closed after all chunks are read');
assert_array_equals(chunks, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'the expected 10 chunks should be read');
});
}, 'ReadableStream integration test: adapting an async pull source');
done();

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="../resources/test-initializer.js"></script>
<script src="../resources/test-utils.js"></script>
<script src="pipe-through.js"></script>
<script>
'use strict';
worker_test('pipe-through.js');
</script>

View file

@ -0,0 +1,108 @@
'use strict';
if (self.importScripts) {
self.importScripts('../resources/test-utils.js');
self.importScripts('/resources/testharness.js');
}
test(() => {
let pipeToArguments;
const thisValue = {
pipeTo() {
pipeToArguments = arguments;
}
};
const input = { readable: {}, writable: {} };
const options = {};
const result = ReadableStream.prototype.pipeThrough.call(thisValue, input, options);
assert_array_equals(pipeToArguments, [input.writable, options],
'correct arguments should be passed to thisValue.pipeTo');
assert_equals(result, input.readable, 'return value should be the passed readable property');
}, 'ReadableStream.prototype.pipeThrough should work generically on its this and its arguments');
test(() => {
const thisValue = {
pipeTo() {
assert_unreached('pipeTo should not be called');
}
};
methodThrows(ReadableStream.prototype, 'pipeThrough', thisValue, [undefined, {}]);
methodThrows(ReadableStream.prototype, 'pipeThrough', thisValue, [null, {}]);
}, 'ReadableStream.prototype.pipeThrough should throw when its first argument is not convertible to an object');
test(() => {
const args = [{ readable: {}, writable: {} }, {}];
methodThrows(ReadableStream.prototype, 'pipeThrough', undefined, args);
methodThrows(ReadableStream.prototype, 'pipeThrough', null, args);
methodThrows(ReadableStream.prototype, 'pipeThrough', 1, args);
methodThrows(ReadableStream.prototype, 'pipeThrough', { pipeTo: 'test' }, args);
}, 'ReadableStream.prototype.pipeThrough should throw when "this" has no pipeTo method');
test(() => {
const error = new Error('potato');
const throwingPipeTo = {
get pipeTo() {
throw error;
}
};
assert_throws(error,
() => ReadableStream.prototype.pipeThrough.call(throwingPipeTo, { readable: { }, writable: { } }, {}),
'pipeThrough should rethrow the error thrown by pipeTo');
const thisValue = {
pipeTo() {
assert_unreached('pipeTo should not be called');
}
};
const throwingWritable = {
readable: {},
get writable() {
throw error;
}
};
assert_throws(error,
() => ReadableStream.prototype.pipeThrough.call(thisValue, throwingWritable, {}),
'pipeThrough should rethrow the error thrown by the writable getter');
const throwingReadable = {
get readable() {
throw error;
},
writable: {}
};
assert_throws(error,
() => ReadableStream.prototype.pipeThrough.call(thisValue, throwingReadable, {}),
'pipeThrough should rethrow the error thrown by the readable getter');
}, 'ReadableStream.prototype.pipeThrough should rethrow errors from accessing pipeTo, readable, or writable');
test(() => {
let count = 0;
const thisValue = {
pipeTo() {
++count;
}
};
ReadableStream.prototype.pipeThrough.call(thisValue, { readable: {}, writable: {} });
ReadableStream.prototype.pipeThrough.call(thisValue, { readable: {} }, {});
ReadableStream.prototype.pipeThrough.call(thisValue, { writable: {} }, {});
assert_equals(count, 3, 'pipeTo was called 3 times');
}, 'ReadableStream.prototype.pipeThrough should work with missing readable, writable, or options');
done();

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="../resources/test-initializer.js"></script>
<script src="readable-stream-reader.js"></script>
<script>
'use strict';
worker_test('readable-stream-reader.js');
</script>

View file

@ -0,0 +1,485 @@
'use strict';
if (self.importScripts) {
self.importScripts('../resources/rs-utils.js');
self.importScripts('/resources/testharness.js');
}
let ReadableStreamReader;
test(() => {
// It's not exposed globally, but we test a few of its properties here.
ReadableStreamReader = (new ReadableStream()).getReader().constructor;
}, 'Can get the ReadableStreamReader constructor indirectly');
test(() => {
assert_throws(new TypeError(), () => new ReadableStreamReader('potato'));
assert_throws(new TypeError(), () => new ReadableStreamReader({}));
assert_throws(new TypeError(), () => new ReadableStreamReader());
}, 'ReadableStreamReader constructor should get a ReadableStream object as argument');
test(() => {
const methods = ['cancel', 'constructor', 'read', 'releaseLock'];
const properties = methods.concat(['closed']).sort();
const rsReader = new ReadableStreamReader(new ReadableStream());
const proto = Object.getPrototypeOf(rsReader);
assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
for (const m of methods) {
const propDesc = Object.getOwnPropertyDescriptor(proto, m);
assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
assert_equals(propDesc.configurable, true, 'method should be configurable');
assert_equals(propDesc.writable, true, 'method should be writable');
assert_equals(typeof rsReader[m], 'function', 'should have be a method');
}
const closedPropDesc = Object.getOwnPropertyDescriptor(proto, 'closed');
assert_equals(closedPropDesc.enumerable, false, 'closed should be non-enumerable');
assert_equals(closedPropDesc.configurable, true, 'closed should be configurable');
assert_not_equals(closedPropDesc.get, undefined, 'closed should have a getter');
assert_equals(closedPropDesc.set, undefined, 'closed should not have a setter');
assert_equals(rsReader.cancel.length, 1, 'cancel has 1 parameter');
assert_not_equals(rsReader.closed, undefined, 'has a non-undefined closed property');
assert_equals(typeof rsReader.closed.then, 'function', 'closed property is thenable');
assert_equals(typeof rsReader.constructor, 'function', 'has a constructor method');
assert_equals(rsReader.constructor.length, 1, 'constructor has 1 parameter');
assert_equals(typeof rsReader.read, 'function', 'has a getReader method');
assert_equals(rsReader.read.length, 0, 'read has no parameters');
assert_equals(typeof rsReader.releaseLock, 'function', 'has a releaseLock method');
assert_equals(rsReader.releaseLock.length, 0, 'releaseLock has no parameters');
}, 'ReadableStreamReader instances should have the correct list of properties');
test(() => {
const rsReader = new ReadableStreamReader(new ReadableStream());
assert_equals(rsReader.closed, rsReader.closed, 'closed should return the same promise');
}, 'ReadableStreamReader closed should always return the same promise object');
test(() => {
const rs = new ReadableStream();
new ReadableStreamReader(rs); // Constructing directly the first time should be fine.
assert_throws(new TypeError(), () => new ReadableStreamReader(rs),
'constructing directly the second time should fail');
}, 'Constructing a ReadableStreamReader directly should fail if the stream is already locked (via direct ' +
'construction)');
test(() => {
const rs = new ReadableStream();
new ReadableStreamReader(rs); // Constructing directly should be fine.
assert_throws(new TypeError(), () => rs.getReader(), 'getReader() should fail');
}, 'Getting a ReadableStreamReader via getReader should fail if the stream is already locked (via direct ' +
'construction)');
test(() => {
const rs = new ReadableStream();
rs.getReader(); // getReader() should be fine.
assert_throws(new TypeError(), () => new ReadableStreamReader(rs), 'constructing directly should fail');
}, 'Constructing a ReadableStreamReader directly should fail if the stream is already locked (via getReader)');
test(() => {
const rs = new ReadableStream();
rs.getReader(); // getReader() should be fine.
assert_throws(new TypeError(), () => rs.getReader(), 'getReader() should fail');
}, 'Getting a ReadableStreamReader via getReader should fail if the stream is already locked (via getReader)');
test(() => {
const rs = new ReadableStream({
start(c) {
c.close();
}
});
new ReadableStreamReader(rs); // Constructing directly should not throw.
}, 'Constructing a ReadableStreamReader directly should be OK if the stream is closed');
test(() => {
const theError = new Error('don\'t say i didn\'t warn ya');
const rs = new ReadableStream({
start(c) {
c.error(theError);
}
});
new ReadableStreamReader(rs); // Constructing directly should not throw.
}, 'Constructing a ReadableStreamReader directly should be OK if the stream is errored');
promise_test(() => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
const reader = rs.getReader();
const promise = reader.read().then(result => {
assert_object_equals(result, { value: 'a', done: false }, 'read() should fulfill with the enqueued chunk');
});
controller.enqueue('a');
return promise;
}, 'Reading from a reader for an empty stream will wait until a chunk is available');
promise_test(() => {
let cancelCalled = false;
const passedReason = new Error('it wasn\'t the right time, sorry');
const rs = new ReadableStream({
cancel(reason) {
assert_true(rs.locked, 'the stream should still be locked');
assert_throws(new TypeError(), () => rs.getReader(), 'should not be able to get another reader');
assert_equals(reason, passedReason, 'the cancellation reason is passed through to the underlying source');
cancelCalled = true;
}
});
const reader = rs.getReader();
return reader.cancel(passedReason).then(() => assert_true(cancelCalled));
}, 'cancel() on a reader does not release the reader');
promise_test(() => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
const reader = rs.getReader();
const promise = reader.closed;
controller.close();
return promise;
}, 'closed should be fulfilled after stream is closed (.closed access before acquiring)');
promise_test(t => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
const reader1 = rs.getReader();
reader1.releaseLock();
const reader2 = rs.getReader();
controller.close();
return Promise.all([
promise_rejects(t, new TypeError(), reader1.closed),
reader2.closed
]);
}, 'closed should be rejected after reader releases its lock (multiple stream locks)');
promise_test(() => {
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
c.enqueue('b');
c.close();
}
});
const reader1 = rs.getReader();
const promise1 = reader1.read().then(r => {
assert_object_equals(r, { value: 'a', done: false }, 'reading the first chunk from reader1 works');
});
reader1.releaseLock();
const reader2 = rs.getReader();
const promise2 = reader2.read().then(r => {
assert_object_equals(r, { value: 'b', done: false }, 'reading the second chunk from reader2 works');
});
reader2.releaseLock();
return Promise.all([promise1, promise2]);
}, 'Multiple readers can access the stream in sequence');
promise_test(() => {
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
}
});
const reader1 = rs.getReader();
reader1.releaseLock();
const reader2 = rs.getReader();
// Should be a no-op
reader1.releaseLock();
return reader2.read().then(result => {
assert_object_equals(result, { value: 'a', done: false },
'read() should still work on reader2 even after reader1 is released');
});
}, 'Cannot use an already-released reader to unlock a stream again');
promise_test(t => {
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
},
cancel() {
assert_unreached('underlying source cancel should not be called');
}
});
const reader = rs.getReader();
reader.releaseLock();
const cancelPromise = reader.cancel();
const reader2 = rs.getReader();
const readPromise = reader2.read().then(r => {
assert_object_equals(r, { value: 'a', done: false }, 'a new reader should be able to read a chunk');
});
return Promise.all([
promise_rejects(t, new TypeError(), cancelPromise),
readPromise
]);
}, 'cancel() on a released reader is a no-op and does not pass through');
promise_test(t => {
const promiseAsserts = [];
let controller;
const theError = { name: 'unique error' };
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
const reader1 = rs.getReader();
promiseAsserts.push(
promise_rejects(t, theError, reader1.closed),
promise_rejects(t, theError, reader1.read())
);
assert_throws(new TypeError(), () => rs.getReader(), 'trying to get another reader before erroring should throw');
controller.error(theError);
reader1.releaseLock();
const reader2 = rs.getReader();
promiseAsserts.push(
promise_rejects(t, theError, reader2.closed),
promise_rejects(t, theError, reader2.read())
);
return Promise.all(promiseAsserts);
}, 'Getting a second reader after erroring the stream and releasing the reader should succeed');
promise_test(t => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
const promise = rs.getReader().closed.then(
t.unreached_func('closed promise should not be fulfilled when stream is errored'),
err => {
assert_equals(err, undefined, 'passed error should be undefined as it was');
}
);
controller.error();
return promise;
}, 'ReadableStreamReader closed promise should be rejected with undefined if that is the error');
promise_test(t => {
const rs = new ReadableStream({
start() {
return Promise.reject();
}
});
return rs.getReader().read().then(
t.unreached_func('read promise should not be fulfilled when stream is errored'),
err => {
assert_equals(err, undefined, 'passed error should be undefined as it was');
}
);
}, 'ReadableStreamReader: if start rejects with no parameter, it should error the stream with an undefined error');
promise_test(t => {
const theError = { name: 'unique string' };
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
const promise = promise_rejects(t, theError, rs.getReader().closed);
controller.error(theError);
return promise;
}, 'Erroring a ReadableStream after checking closed should reject ReadableStreamReader closed promise');
promise_test(t => {
const theError = { name: 'unique string' };
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
controller.error(theError);
// Let's call getReader twice for extra test coverage of this code path.
rs.getReader().releaseLock();
return promise_rejects(t, theError, rs.getReader().closed);
}, 'Erroring a ReadableStream before checking closed should reject ReadableStreamReader closed promise');
promise_test(() => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
const reader = rs.getReader();
const promise = Promise.all([
reader.read().then(result => {
assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (1)');
}),
reader.read().then(result => {
assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (2)');
}),
reader.closed
]);
controller.close();
return promise;
}, 'Reading twice on a stream that gets closed');
promise_test(() => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
controller.close();
const reader = rs.getReader();
return Promise.all([
reader.read().then(result => {
assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (1)');
}),
reader.read().then(result => {
assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (2)');
}),
reader.closed
]);
}, 'Reading twice on a closed stream');
promise_test(t => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
const myError = { name: 'mashed potatoes' };
controller.error(myError);
const reader = rs.getReader();
return Promise.all([
promise_rejects(t, myError, reader.read()),
promise_rejects(t, myError, reader.read()),
promise_rejects(t, myError, reader.closed)
]);
}, 'Reading twice on an errored stream');
promise_test(t => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
const myError = { name: 'mashed potatoes' };
const reader = rs.getReader();
const promise = Promise.all([
promise_rejects(t, myError, reader.read()),
promise_rejects(t, myError, reader.read()),
promise_rejects(t, myError, reader.closed)
]);
controller.error(myError);
return promise;
}, 'Reading twice on a stream that gets errored');
done();

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="../resources/test-initializer.js"></script>
<script src="../resources/rs-utils.js"></script>
<script src="tee.js"></script>
<script>
'use strict';
worker_test('tee.js');
</script>

View file

@ -0,0 +1,254 @@
'use strict';
if (self.importScripts) {
self.importScripts('../resources/rs-utils.js');
self.importScripts('/resources/testharness.js');
}
test(() => {
const rs = new ReadableStream();
const result = rs.tee();
assert_true(Array.isArray(result), 'return value should be an array');
assert_equals(result.length, 2, 'array should have length 2');
assert_equals(result[0].constructor, ReadableStream, '0th element should be a ReadableStream');
assert_equals(result[1].constructor, ReadableStream, '1st element should be a ReadableStream');
}, 'ReadableStream teeing: rs.tee() returns an array of two ReadableStreams');
promise_test(t => {
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
c.enqueue('b');
c.close();
}
});
const branch = rs.tee();
const branch1 = branch[0];
const branch2 = branch[1];
const reader1 = branch1.getReader();
const reader2 = branch2.getReader();
reader2.closed.then(t.unreached_func('branch2 should not be closed'));
return Promise.all([
reader1.closed,
reader1.read().then(r => {
assert_object_equals(r, { value: 'a', done: false }, 'first chunk from branch1 should be correct');
}),
reader1.read().then(r => {
assert_object_equals(r, { value: 'b', done: false }, 'second chunk from branch1 should be correct');
}),
reader1.read().then(r => {
assert_object_equals(r, { value: undefined, done: true }, 'third read() from branch1 should be done');
}),
reader2.read().then(r => {
assert_object_equals(r, { value: 'a', done: false }, 'first chunk from branch2 should be correct');
})
]);
}, 'ReadableStream teeing: should be able to read one branch to the end without affecting the other');
promise_test(() => {
const theObject = { the: 'test object' };
const rs = new ReadableStream({
start(c) {
c.enqueue(theObject);
}
});
const branch = rs.tee();
const branch1 = branch[0];
const branch2 = branch[1];
const reader1 = branch1.getReader();
const reader2 = branch2.getReader();
return Promise.all([reader1.read(), reader2.read()]).then(values => {
assert_object_equals(values[0], values[1], 'the values should be equal');
});
}, 'ReadableStream teeing: values should be equal across each branch');
promise_test(t => {
const theError = { name: 'boo!' };
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
c.enqueue('b');
},
pull() {
throw theError;
}
});
const branches = rs.tee();
const reader1 = branches[0].getReader();
const reader2 = branches[1].getReader();
reader1.label = 'reader1';
reader2.label = 'reader2';
return Promise.all([
promise_rejects(t, theError, reader1.closed),
promise_rejects(t, theError, reader2.closed),
reader1.read().then(r => {
assert_object_equals(r, { value: 'a', done: false }, 'should be able to read the first chunk in branch1');
}),
reader1.read().then(r => {
assert_object_equals(r, { value: 'b', done: false }, 'should be able to read the second chunk in branch1');
return promise_rejects(t, theError, reader2.read());
})
.then(() => promise_rejects(t, theError, reader1.read()))
]);
}, 'ReadableStream teeing: errors in the source should propagate to both branches');
promise_test(() => {
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
c.enqueue('b');
c.close();
}
});
const branches = rs.tee();
const branch1 = branches[0];
const branch2 = branches[1];
branch1.cancel();
return Promise.all([
readableStreamToArray(branch1).then(chunks => {
assert_array_equals(chunks, [], 'branch1 should have no chunks');
}),
readableStreamToArray(branch2).then(chunks => {
assert_array_equals(chunks, ['a', 'b'], 'branch2 should have two chunks');
})
]);
}, 'ReadableStream teeing: canceling branch1 should not impact branch2');
promise_test(() => {
const rs = new ReadableStream({
start(c) {
c.enqueue('a');
c.enqueue('b');
c.close();
}
});
const branches = rs.tee();
const branch1 = branches[0];
const branch2 = branches[1];
branch2.cancel();
return Promise.all([
readableStreamToArray(branch1).then(chunks => {
assert_array_equals(chunks, ['a', 'b'], 'branch1 should have two chunks');
}),
readableStreamToArray(branch2).then(chunks => {
assert_array_equals(chunks, [], 'branch2 should have no chunks');
})
]);
}, 'ReadableStream teeing: canceling branch2 should not impact branch2');
promise_test(() => {
const reason1 = new Error('We\'re wanted men.');
const reason2 = new Error('I have the death sentence on twelve systems.');
let resolve;
const promise = new Promise(r => resolve = r);
const rs = new ReadableStream({
cancel(reason) {
assert_array_equals(reason, [reason1, reason2],
'the cancel reason should be an array containing those from the branches');
resolve();
}
});
const branch = rs.tee();
const branch1 = branch[0];
const branch2 = branch[1];
branch1.cancel(reason1);
branch2.cancel(reason2);
return promise;
}, 'ReadableStream teeing: canceling both branches should aggregate the cancel reasons into an array');
promise_test(t => {
const theError = { name: 'I\'ll be careful.' };
const rs = new ReadableStream({
cancel() {
throw theError;
}
});
const branch = rs.tee();
const branch1 = branch[0];
const branch2 = branch[1];
return Promise.all([
promise_rejects(t, theError, branch1.cancel()),
promise_rejects(t, theError, branch2.cancel())
]);
}, 'ReadableStream teeing: failing to cancel the original stream should cause cancel() to reject on branches');
promise_test(() => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
const branches = rs.tee();
const reader1 = branches[0].getReader();
const reader2 = branches[1].getReader();
const promise = Promise.all([reader1.closed, reader2.closed]);
controller.close();
return promise;
}, 'ReadableStream teeing: closing the original should immediately close the branches');
promise_test(t => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
}
});
const branches = rs.tee();
const reader1 = branches[0].getReader();
const reader2 = branches[1].getReader();
const theError = { name: 'boo!' };
const promise = Promise.all([
promise_rejects(t, theError, reader1.closed),
promise_rejects(t, theError, reader2.closed)
]);
controller.error(theError);
return promise;
}, 'ReadableStream teeing: erroring the original should immediately error the branches');
done();

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script src="../resources/test-initializer.js"></script>
<script src="../resources/test-utils.js"></script>
<script src="../resources/rs-test-templates.js"></script>
<script src="templated.js"></script>
<script>
'use strict';
worker_test('templated.js');
</script>

View file

@ -0,0 +1,148 @@
'use strict';
if (self.importScripts) {
self.importScripts('/resources/testharness.js');
self.importScripts('../resources/test-utils.js');
self.importScripts('../resources/rs-test-templates.js');
}
// Run the readable stream test templates against readable streams created directly using the constructor
const theError = { name: 'boo!' };
const chunks = ['a', 'b'];
templatedRSEmpty('ReadableStream (empty)', () => {
return new ReadableStream();
});
templatedRSEmptyReader('ReadableStream (empty) reader', () => {
return streamAndDefaultReader(new ReadableStream());
});
templatedRSClosed('ReadableStream (closed via call in start)', () => {
return new ReadableStream({
start(c) {
c.close();
}
});
});
templatedRSClosedReader('ReadableStream reader (closed before getting reader)', () => {
let controller;
const stream = new ReadableStream({
start(c) {
controller = c;
}
});
controller.close();
const result = streamAndDefaultReader(stream);
return result;
});
templatedRSClosedReader('ReadableStream reader (closed after getting reader)', () => {
let controller;
const stream = new ReadableStream({
start(c) {
controller = c;
}
});
const result = streamAndDefaultReader(stream);
controller.close();
return result;
});
templatedRSClosed('ReadableStream (closed via cancel)', () => {
const stream = new ReadableStream();
stream.cancel();
return stream;
});
templatedRSClosedReader('ReadableStream reader (closed via cancel after getting reader)', () => {
const stream = new ReadableStream();
const result = streamAndDefaultReader(stream);
result.reader.cancel();
return result;
});
templatedRSErrored('ReadableStream (errored via call in start)', () => {
return new ReadableStream({
start(c) {
c.error(theError);
}
});
}, theError);
templatedRSErroredSyncOnly('ReadableStream (errored via call in start)', () => {
return new ReadableStream({
start(c) {
c.error(theError);
}
});
}, theError);
templatedRSErrored('ReadableStream (errored via returning a rejected promise in start)', () => {
return new ReadableStream({
start() {
return Promise.reject(theError);
}
});
}, theError);
templatedRSErroredReader('ReadableStream (errored via returning a rejected promise in start) reader', () => {
return streamAndDefaultReader(new ReadableStream({
start() {
return Promise.reject(theError);
}
}));
}, theError);
templatedRSErroredReader('ReadableStream reader (errored before getting reader)', () => {
let controller;
const stream = new ReadableStream({
start(c) {
controller = c;
}
});
controller.error(theError);
return streamAndDefaultReader(stream);
}, theError);
templatedRSErroredReader('ReadableStream reader (errored after getting reader)', () => {
let controller;
const result = streamAndDefaultReader(new ReadableStream({
start(c) {
controller = c;
}
}));
controller.error(theError);
return result;
}, theError);
templatedRSTwoChunksOpenReader('ReadableStream (two chunks enqueued, still open) reader', () => {
return streamAndDefaultReader(new ReadableStream({
start(c) {
c.enqueue(chunks[0]);
c.enqueue(chunks[1]);
}
}));
}, chunks);
templatedRSTwoChunksClosedReader('ReadableStream (two chunks enqueued, then closed) reader', () => {
let doClose;
const stream = new ReadableStream({
start(c) {
c.enqueue(chunks[0]);
c.enqueue(chunks[1]);
doClose = c.close.bind(c);
}
});
const result = streamAndDefaultReader(stream);
doClose();
return result;
}, chunks);
function streamAndDefaultReader(stream) {
return { stream, reader: stream.getReader() };
}
done();

View file

@ -0,0 +1,634 @@
'use strict';
// These tests can be run against any readable stream produced by the web platform that meets the given descriptions.
// For readable stream tests, the factory should return the stream. For reader tests, the factory should return a
// { stream, reader } object. (You can use this to vary the time at which you acquire a reader.)
self.templatedRSEmpty = (label, factory) => {
test(() => {}, 'Running templatedRSEmpty with ' + label);
test(() => {
const rs = factory();
assert_equals(typeof rs.locked, 'boolean', 'has a boolean locked getter');
assert_equals(typeof rs.cancel, 'function', 'has a cancel method');
assert_equals(typeof rs.getReader, 'function', 'has a getReader method');
assert_equals(typeof rs.pipeThrough, 'function', 'has a pipeThrough method');
assert_equals(typeof rs.pipeTo, 'function', 'has a pipeTo method');
assert_equals(typeof rs.tee, 'function', 'has a tee method');
}, 'instances have the correct methods and properties');
test(() => {
const rs = factory();
assert_throws(new RangeError(), () => rs.getReader({ mode: '' }), 'empty string mode should throw');
assert_throws(new RangeError(), () => rs.getReader({ mode: null }), 'null mode should throw');
assert_throws(new RangeError(), () => rs.getReader({ mode: 'asdf' }), 'asdf mode should throw');
assert_throws(new TypeError(), () => rs.getReader(null), 'null should throw');
}, 'calling getReader with invalid arguments should throw appropriate errors');
};
self.templatedRSClosed = (label, factory) => {
test(() => {}, 'Running templatedRSClosed with ' + label);
promise_test(() => {
const rs = factory();
const cancelPromise1 = rs.cancel();
const cancelPromise2 = rs.cancel();
assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
return Promise.all([
cancelPromise1.then(v => assert_equals(v, undefined, 'first cancel() call should fulfill with undefined')),
cancelPromise2.then(v => assert_equals(v, undefined, 'second cancel() call should fulfill with undefined'))
]);
}, 'cancel() should return a distinct fulfilled promise each time');
test(() => {
const rs = factory();
assert_false(rs.locked, 'locked getter should return false');
}, 'locked should be false');
test(() => {
const rs = factory();
rs.getReader(); // getReader() should not throw.
}, 'getReader() should be OK');
test(() => {
const rs = factory();
const reader = rs.getReader();
reader.releaseLock();
const reader2 = rs.getReader(); // Getting a second reader should not throw.
reader2.releaseLock();
rs.getReader(); // Getting a third reader should not throw.
}, 'should be able to acquire multiple readers if they are released in succession');
test(() => {
const rs = factory();
rs.getReader();
assert_throws(new TypeError(), () => rs.getReader(), 'getting a second reader should throw');
assert_throws(new TypeError(), () => rs.getReader(), 'getting a third reader should throw');
}, 'should not be able to acquire a second reader if we don\'t release the first one');
};
self.templatedRSErrored = (label, factory, error) => {
test(() => {}, 'Running templatedRSErrored with ' + label);
promise_test(t => {
const rs = factory();
const reader = rs.getReader();
return Promise.all([
promise_rejects(t, error, reader.closed),
promise_rejects(t, error, reader.read())
]);
}, 'getReader() should return a reader that acts errored');
promise_test(t => {
const rs = factory();
const reader = rs.getReader();
return Promise.all([
promise_rejects(t, error, reader.read()),
promise_rejects(t, error, reader.read()),
promise_rejects(t, error, reader.closed)
]);
}, 'read() twice should give the error each time');
test(() => {
const rs = factory();
assert_false(rs.locked, 'locked getter should return false');
}, 'locked should be false');
};
self.templatedRSErroredSyncOnly = (label, factory, error) => {
test(() => {}, 'Running templatedRSErroredSyncOnly with ' + label);
promise_test(t => {
const rs = factory();
rs.getReader().releaseLock();
const reader = rs.getReader(); // Calling getReader() twice does not throw (the stream is not locked).
return promise_rejects(t, error, reader.closed);
}, 'should be able to obtain a second reader, with the correct closed promise');
test(() => {
const rs = factory();
rs.getReader();
assert_throws(new TypeError(), () => rs.getReader(), 'getting a second reader should throw a TypeError');
assert_throws(new TypeError(), () => rs.getReader(), 'getting a third reader should throw a TypeError');
}, 'should not be able to obtain additional readers if we don\'t release the first lock');
promise_test(t => {
const rs = factory();
const cancelPromise1 = rs.cancel();
const cancelPromise2 = rs.cancel();
assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
return Promise.all([
promise_rejects(t, error, cancelPromise1),
promise_rejects(t, error, cancelPromise2)
]);
}, 'cancel() should return a distinct rejected promise each time');
promise_test(t => {
const rs = factory();
const reader = rs.getReader();
const cancelPromise1 = reader.cancel();
const cancelPromise2 = reader.cancel();
assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
return Promise.all([
promise_rejects(t, error, cancelPromise1),
promise_rejects(t, error, cancelPromise2)
]);
}, 'reader cancel() should return a distinct rejected promise each time');
};
self.templatedRSEmptyReader = (label, factory) => {
test(() => {}, 'Running templatedRSEmptyReader with ' + label);
test(() => {
const reader = factory().reader;
assert_true('closed' in reader, 'has a closed property');
assert_equals(typeof reader.closed.then, 'function', 'closed property is thenable');
assert_equals(typeof reader.cancel, 'function', 'has a cancel method');
assert_equals(typeof reader.read, 'function', 'has a read method');
assert_equals(typeof reader.releaseLock, 'function', 'has a releaseLock method');
}, 'instances have the correct methods and properties');
test(() => {
const stream = factory().stream;
assert_true(stream.locked, 'locked getter should return true');
}, 'locked should be true');
promise_test(t => {
const reader = factory().reader;
reader.read().then(
t.unreached_func('read() should not fulfill'),
t.unreached_func('read() should not reject')
);
return delay(500);
}, 'read() should never settle');
promise_test(t => {
const reader = factory().reader;
reader.read().then(
t.unreached_func('read() should not fulfill'),
t.unreached_func('read() should not reject')
);
reader.read().then(
t.unreached_func('read() should not fulfill'),
t.unreached_func('read() should not reject')
);
return delay(500);
}, 'two read()s should both never settle');
test(() => {
const reader = factory().reader;
assert_not_equals(reader.read(), reader.read(), 'the promises returned should be distinct');
}, 'read() should return distinct promises each time');
test(() => {
const stream = factory().stream;
assert_throws(new TypeError(), () => stream.getReader(), 'stream.getReader() should throw a TypeError');
}, 'getReader() again on the stream should fail');
promise_test(t => {
const streamAndReader = factory();
const stream = streamAndReader.stream;
const reader = streamAndReader.reader;
reader.read().then(
t.unreached_func('first read() should not fulfill'),
t.unreached_func('first read() should not reject')
);
reader.read().then(
t.unreached_func('second read() should not fulfill'),
t.unreached_func('second read() should not reject')
);
reader.closed.then(
t.unreached_func('closed should not fulfill'),
t.unreached_func('closed should not reject')
);
assert_throws(new TypeError(), () => reader.releaseLock(), 'releaseLock should throw a TypeError');
assert_true(stream.locked, 'the stream should still be locked');
return delay(500);
}, 'releasing the lock with pending read requests should throw but the read requests should stay pending');
promise_test(t => {
const reader = factory().reader;
reader.releaseLock();
return Promise.all([
promise_rejects(t, new TypeError(), reader.read()),
promise_rejects(t, new TypeError(), reader.read())
]);
}, 'releasing the lock should cause further read() calls to reject with a TypeError');
promise_test(t => {
const reader = factory().reader;
const closedBefore = reader.closed;
reader.releaseLock();
const closedAfter = reader.closed;
assert_equals(closedBefore, closedAfter, 'the closed promise should not change identity');
return promise_rejects(t, new TypeError(), closedBefore);
}, 'releasing the lock should cause closed calls to reject with a TypeError');
test(() => {
const streamAndReader = factory();
const stream = streamAndReader.stream;
const reader = streamAndReader.reader;
reader.releaseLock();
assert_false(stream.locked, 'locked getter should return false');
}, 'releasing the lock should cause locked to become false');
promise_test(() => {
const reader = factory().reader;
reader.cancel();
return reader.read().then(r => {
assert_object_equals(r, { value: undefined, done: true }, 'read()ing from the reader should give a done result');
});
}, 'canceling via the reader should cause the reader to act closed');
promise_test(t => {
const stream = factory().stream;
return promise_rejects(t, new TypeError(), stream.cancel());
}, 'canceling via the stream should fail');
};
self.templatedRSClosedReader = (label, factory) => {
test(() => {}, 'Running templatedRSClosedReader with ' + label);
promise_test(() => {
const reader = factory().reader;
return reader.read().then(v => {
assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
});
}, 'read() should fulfill with { value: undefined, done: true }');
promise_test(() => {
const reader = factory().reader;
return Promise.all([
reader.read().then(v => {
assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
}),
reader.read().then(v => {
assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
})
]);
}, 'read() multiple times should fulfill with { value: undefined, done: true }');
promise_test(() => {
const reader = factory().reader;
return reader.read().then(() => reader.read()).then(v => {
assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
});
}, 'read() should work when used within another read() fulfill callback');
promise_test(() => {
const reader = factory().reader;
return reader.closed.then(v => assert_equals(v, undefined, 'reader closed should fulfill with undefined'));
}, 'closed should fulfill with undefined');
promise_test(t => {
const reader = factory().reader;
const closedBefore = reader.closed;
reader.releaseLock();
const closedAfter = reader.closed;
assert_not_equals(closedBefore, closedAfter, 'the closed promise should change identity');
return Promise.all([
closedBefore.then(v => assert_equals(v, undefined, 'reader.closed acquired before release should fulfill')),
promise_rejects(t, new TypeError(), closedAfter)
]);
}, 'releasing the lock should cause closed to reject and change identity');
promise_test(() => {
const reader = factory().reader;
const cancelPromise1 = reader.cancel();
const cancelPromise2 = reader.cancel();
const closedReaderPromise = reader.closed;
assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
assert_not_equals(cancelPromise1, closedReaderPromise, 'cancel() promise 1 should be distinct from reader.closed');
assert_not_equals(cancelPromise2, closedReaderPromise, 'cancel() promise 2 should be distinct from reader.closed');
return Promise.all([
cancelPromise1.then(v => assert_equals(v, undefined, 'first cancel() should fulfill with undefined')),
cancelPromise2.then(v => assert_equals(v, undefined, 'second cancel() should fulfill with undefined'))
]);
}, 'cancel() should return a distinct fulfilled promise each time');
};
self.templatedRSErroredReader = (label, factory, error) => {
test(() => {}, 'Running templatedRSErroredReader with ' + label);
promise_test(t => {
const reader = factory().reader;
return promise_rejects(t, error, reader.closed);
}, 'closed should reject with the error');
promise_test(t => {
const reader = factory().reader;
const closedBefore = reader.closed;
return promise_rejects(t, error, closedBefore).then(() => {
reader.releaseLock();
const closedAfter = reader.closed;
assert_not_equals(closedBefore, closedAfter, 'the closed promise should change identity');
return promise_rejects(t, new TypeError(), closedAfter);
});
}, 'releasing the lock should cause closed to reject and change identity');
promise_test(t => {
const reader = factory().reader;
return promise_rejects(t, error, reader.read());
}, 'read() should reject with the error');
};
self.templatedRSTwoChunksOpenReader = (label, factory, chunks) => {
test(() => {}, 'Running templatedRSTwoChunksOpenReader with ' + label);
promise_test(() => {
const reader = factory().reader;
return Promise.all([
reader.read().then(r => {
assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
}),
reader.read().then(r => {
assert_object_equals(r, { value: chunks[1], done: false }, 'second result should be correct');
})
]);
}, 'calling read() twice without waiting will eventually give both chunks (sequential)');
promise_test(() => {
const reader = factory().reader;
return reader.read().then(r => {
assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
return reader.read().then(r2 => {
assert_object_equals(r2, { value: chunks[1], done: false }, 'second result should be correct');
});
});
}, 'calling read() twice without waiting will eventually give both chunks (nested)');
test(() => {
const reader = factory().reader;
assert_not_equals(reader.read(), reader.read(), 'the promises returned should be distinct');
}, 'read() should return distinct promises each time');
promise_test(() => {
const reader = factory().reader;
const promise1 = reader.closed.then(v => {
assert_equals(v, undefined, 'reader closed should fulfill with undefined');
});
const promise2 = reader.read().then(r => {
assert_object_equals(r, { value: chunks[0], done: false },
'promise returned before cancellation should fulfill with a chunk');
});
reader.cancel();
const promise3 = reader.read().then(r => {
assert_object_equals(r, { value: undefined, done: true },
'promise returned after cancellation should fulfill with an end-of-stream signal');
});
return Promise.all([promise1, promise2, promise3]);
}, 'cancel() after a read() should still give that single read result');
};
self.templatedRSTwoChunksClosedReader = function (label, factory, chunks) {
test(() => {}, 'Running templatedRSTwoChunksClosedReader with ' + label);
promise_test(() => {
const reader = factory().reader;
return Promise.all([
reader.read().then(r => {
assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
}),
reader.read().then(r => {
assert_object_equals(r, { value: chunks[1], done: false }, 'second result should be correct');
}),
reader.read().then(r => {
assert_object_equals(r, { value: undefined, done: true }, 'third result should be correct');
})
]);
}, 'third read(), without waiting, should give { value: undefined, done: true } (sequential)');
promise_test(() => {
const reader = factory().reader;
return reader.read().then(r => {
assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
return reader.read().then(r2 => {
assert_object_equals(r2, { value: chunks[1], done: false }, 'second result should be correct');
return reader.read().then(r3 => {
assert_object_equals(r3, { value: undefined, done: true }, 'third result should be correct');
});
});
});
}, 'third read(), without waiting, should give { value: undefined, done: true } (nested)');
promise_test(() => {
const streamAndReader = factory();
const stream = streamAndReader.stream;
const reader = streamAndReader.reader;
assert_true(stream.locked, 'stream should start locked');
const promise = reader.closed.then(v => {
assert_equals(v, undefined, 'reader closed should fulfill with undefined');
assert_true(stream.locked, 'stream should remain locked');
});
reader.read();
reader.read();
return promise;
}, 'draining the stream via read() should cause the reader closed promise to fulfill, but locked stays true');
promise_test(() => {
const streamAndReader = factory();
const stream = streamAndReader.stream;
const reader = streamAndReader.reader;
const promise = reader.closed.then(() => {
assert_true(stream.locked, 'the stream should start locked');
reader.releaseLock(); // Releasing the lock after reader closed should not throw.
assert_false(stream.locked, 'the stream should end unlocked');
});
reader.read();
reader.read();
return promise;
}, 'releasing the lock after the stream is closed should cause locked to become false');
promise_test(t => {
const reader = factory().reader;
reader.releaseLock();
return Promise.all([
promise_rejects(t, new TypeError(), reader.read()),
promise_rejects(t, new TypeError(), reader.read()),
promise_rejects(t, new TypeError(), reader.read())
]);
}, 'releasing the lock should cause further read() calls to reject with a TypeError');
promise_test(() => {
const streamAndReader = factory();
const stream = streamAndReader.stream;
const reader = streamAndReader.reader;
const readerClosed = reader.closed;
assert_equals(reader.closed, readerClosed, 'accessing reader.closed twice in succession gives the same value');
const promise = reader.read().then(() => {
assert_equals(reader.closed, readerClosed, 'reader.closed is the same after read() fulfills');
reader.releaseLock();
assert_equals(reader.closed, readerClosed, 'reader.closed is the same after releasing the lock');
const newReader = stream.getReader();
return newReader.read();
});
assert_equals(reader.closed, readerClosed, 'reader.closed is the same after calling read()');
return promise;
}, 'reader\'s closed property always returns the same promise');
};

View file

@ -0,0 +1,185 @@
'use strict';
(function () {
class RandomPushSource {
constructor(toPush) {
this.pushed = 0;
this.toPush = toPush;
this.started = false;
this.paused = false;
this.closed = false;
this._intervalHandle = null;
}
readStart() {
if (this.closed) {
return;
}
if (!this.started) {
this._intervalHandle = setInterval(writeChunk, 2);
this.started = true;
}
if (this.paused) {
this._intervalHandle = setInterval(writeChunk, 2);
this.paused = false;
}
const source = this;
function writeChunk() {
if (source.paused) {
return;
}
source.pushed++;
if (source.toPush > 0 && source.pushed > source.toPush) {
if (source._intervalHandle) {
clearInterval(source._intervalHandle);
source._intervalHandle = undefined;
}
source.closed = true;
source.onend();
} else {
source.ondata(randomChunk(128));
}
}
}
readStop() {
if (this.paused) {
return;
}
if (this.started) {
this.paused = true;
clearInterval(this._intervalHandle);
this._intervalHandle = undefined;
} else {
throw new Error('Can\'t pause reading an unstarted source.');
}
}
}
function randomChunk(size) {
let chunk = '';
for (let i = 0; i < size; ++i) {
// Add a random character from the basic printable ASCII set.
chunk += String.fromCharCode(Math.round(Math.random() * 84) + 32);
}
return chunk;
}
function readableStreamToArray(readable, reader) {
if (reader === undefined) {
reader = readable.getReader();
}
const chunks = [];
return pump();
function pump() {
return reader.read().then(result => {
if (result.done) {
return chunks;
}
chunks.push(result.value);
return pump();
});
}
}
class SequentialPullSource {
constructor(limit, options) {
const async = options && options.async;
this.current = 0;
this.limit = limit;
this.opened = false;
this.closed = false;
this._exec = f => f();
if (async) {
this._exec = f => setTimeout(f, 0);
}
}
open(cb) {
this._exec(() => {
this.opened = true;
cb();
});
}
read(cb) {
this._exec(() => {
if (++this.current <= this.limit) {
cb(null, false, this.current);
} else {
cb(null, true, null);
}
});
}
close(cb) {
this._exec(() => {
this.closed = true;
cb();
});
}
}
function sequentialReadableStream(limit, options) {
const sequentialSource = new SequentialPullSource(limit, options);
const stream = new ReadableStream({
start() {
return new Promise((resolve, reject) => {
sequentialSource.open(err => {
if (err) {
reject(err);
}
resolve();
});
});
},
pull(c) {
return new Promise((resolve, reject) => {
sequentialSource.read((err, done, chunk) => {
if (err) {
reject(err);
} else if (done) {
sequentialSource.close(err2 => {
if (err2) {
reject(err2);
}
c.close();
resolve();
});
} else {
c.enqueue(chunk);
resolve();
}
});
});
}
});
stream.source = sequentialSource;
return stream;
}
self.RandomPushSource = RandomPushSource;
self.readableStreamToArray = readableStreamToArray;
self.sequentialReadableStream = sequentialReadableStream;
}());

View file

@ -0,0 +1,14 @@
'use strict';
function worker_test(file) {
fetch_tests_from_worker(new Worker(file));
if (typeof SharedWorker === 'function') {
fetch_tests_from_worker(new SharedWorker(file));
} else {
test(() => {
assert_unreached('SharedWorker is unavailable');
}, 'Load ' + file + ' with SharedWorker');
}
service_worker_test(file);
}

View file

@ -0,0 +1,43 @@
'use strict';
self.getterRejects = (t, obj, getterName, target) => {
const getter = Object.getOwnPropertyDescriptor(obj, getterName).get;
return promise_rejects(t, new TypeError(), getter.call(target));
};
self.methodRejects = (t, obj, methodName, target) => {
const method = obj[methodName];
return promise_rejects(t, new TypeError(), method.call(target));
};
self.getterThrows = (obj, getterName, target) => {
const getter = Object.getOwnPropertyDescriptor(obj, getterName).get;
assert_throws(new TypeError(), () => getter.call(target), getterName + ' should throw a TypeError');
};
self.methodThrows = (obj, methodName, target, args) => {
const method = obj[methodName];
assert_throws(new TypeError(), () => method.apply(target, args), methodName + ' should throw a TypeError');
};
self.garbageCollect = () => {
if (self.gc) {
// Use --expose_gc for V8 (and Node.js)
// Exposed in SpiderMonkey shell as well
self.gc();
} else if (self.GCController) {
// Present in some WebKit development environments
GCController.collect();
} else {
/* eslint-disable no-console */
console.warn('Tests are running without the ability to do manual garbage collection. They will still work, but ' +
'coverage will be suboptimal.');
/* eslint-enable no-console */
}
};
self.delay = ms => new Promise(resolve => step_timeout(resolve, ms));