Skip to content

Commit

Permalink
Writable stream updates
Browse files Browse the repository at this point in the history
Passes most tests except blocked on jsdom/webidl2js#123 (comment) and jsdom/webidl2js#79
  • Loading branch information
domenic committed Apr 15, 2020
1 parent 2304a01 commit 504f25d
Show file tree
Hide file tree
Showing 21 changed files with 714 additions and 871 deletions.
13 changes: 12 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@ SHELL=/bin/bash -o pipefail
.PHONY: local remote deploy review

remote: index.bs
curl https://api.csswg.org/bikeshed/ -f -F file=@index.bs > index.html -F md-Text-Macro="COMMIT-SHA LOCAL COPY"
@ (HTTP_STATUS=$$(curl https://api.csswg.org/bikeshed/ \
--output index.html \
--write-out "%{http_code}" \
--header "Accept: text/plain, text/html" \
-F die-on=warning \
-F md-Text-Macro="COMMIT-SHA LOCAL COPY" \
-F file=@index.bs) && \
[[ "$$HTTP_STATUS" -eq "200" ]]) || ( \
echo ""; cat index.html; echo ""; \
rm -f index.html; \
exit 22 \
);

local: index.bs
bikeshed spec index.bs index.html --md-Text-Macro="COMMIT-SHA LOCAL COPY"
Expand Down
24 changes: 12 additions & 12 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -3756,7 +3756,7 @@ as such the counterpart internal methods are used polymorphically.
id="ws-default-controller-private-error">\[[ErrorSteps]]()</dfn> implements the
[$WritableStreamController/[[ErrorSteps]]$] contract. It performs the following steps:

1. Perform ! ResetQueue([=this=]).
1. Perform ! [$ResetQueue$]([=this=]).
</div>

<h3 id="ws-all-abstract-ops">Abstract operations</h3>
Expand Down Expand Up @@ -4392,6 +4392,15 @@ The following abstract operations support the implementation of the
[$WritableStreamDefaultControllerError$](|controller|, |error|).
</div>

<div algorithm>
<dfn abstract-op lt="WritableStreamDefaultControllerGetBackpressure"
id="writable-stream-default-controller-get-backpressure">WritableStreamDefaultControllerGetBackpressure(|controller|)</dfn>
performs the following steps:

1. Let |desiredSize| be ! [$WritableStreamDefaultControllerGetDesiredSize$](|controller|).
1. Return true if |desiredSize| ≤ 0, or false otherwise.
</div>

<div algorithm>
<dfn abstract-op lt="WritableStreamDefaultControllerGetChunkSize"
id="writable-stream-default-controller-get-chunk-size">WritableStreamDefaultControllerGetChunkSize(|controller|,
Expand All @@ -4406,15 +4415,6 @@ The following abstract operations support the implementation of the
1. Return |returnValue|.\[[Value]].
</div>

<div algorithm>
<dfn abstract-op lt="WritableStreamDefaultControllerGetBackpressure"
id="writable-stream-default-controller-get-backpressure">WritableStreamDefaultControllerGetBackpressure(|controller|)</dfn>
performs the following steps:

1. Let |desiredSize| be ! [$WritableStreamDefaultControllerGetDesiredSize$](|controller|).
1. Return true if |desiredSize| ≤ 0, or false otherwise.
</div>

<div algorithm>
<dfn abstract-op lt="WritableStreamDefaultControllerGetDesiredSize"
id="writable-stream-default-controller-get-desired-size">WritableStreamDefaultControllerGetDesiredSize(|controller|)</dfn>
Expand Down Expand Up @@ -5549,8 +5549,8 @@ trillions of chunks are enqueued.)
following steps:

1. Assert: |container| has \[[queue]] and \[[queueTotalSize]] internal slots.
1. If ! [$IsNonNegativeNumber$](|v|) is false, throw a {{RangeError}} exception.
1. If |v| is +∞, throw a {{RangeError}} exception.
1. If ! [$IsNonNegativeNumber$](|size|) is false, throw a {{RangeError}} exception.
1. If |size| is +∞, throw a {{RangeError}} exception.
1. [=list/Append=] Record {\[[value]]: |value|, \[[size]]: |size|} to |container|.\[[queue]].
1. Set |container|.\[[queueTotalSize]] to |container|.\[[queueTotalSize]] + |size|.
</div>
Expand Down
12 changes: 4 additions & 8 deletions reference-implementation/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,15 @@
"env": {
"node": true,
"browser": true,
"es6": true
"es2020": true
},
"parserOptions": {
"ecmaVersion": 2018
"ecmaVersion": 2020
},
"globals": {
"ReadableStream": false,
"WritableStream": false,
"TransformStream": false,
"ByteLengthQueuingStrategy": false,
"CountQueuingStrategy": false,
"GCController": false,
"gc": false
"gc": false,
"globalThis": false
},
"rules": {
// Possible errors
Expand Down
3 changes: 2 additions & 1 deletion reference-implementation/compile-idl.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const output = path.resolve(__dirname, './generated');
mkdirSync(output, { recursive: true });

const transformer = new Transformer({
implSuffix: '-impl'
implSuffix: '-impl',
suppressErrors: true // until https://github.com/jsdom/webidl2js/pull/123 lands
});

transformer.addSource(input, input);
Expand Down
6 changes: 6 additions & 0 deletions reference-implementation/lib/QueuingStrategy.webidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dictionary QueuingStrategy {
unrestricted double highWaterMark;
QueuingStrategySize size;
};

callback QueuingStrategySize = unrestricted double (optional any chunk);
12 changes: 12 additions & 0 deletions reference-implementation/lib/UnderlyingSink.webidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
dictionary UnderlyingSink {
WritableStreamStartCallback start;
WritableStreamWriteCallback write;
WritableStreamCloseCallback close;
WritableStreamAbortCallback abort;
any type;
};

callback WritableStreamStartCallback = Promise<void> (WritableStreamDefaultController controller);
callback WritableStreamWriteCallback = Promise<void> (WritableStreamDefaultController controller, optional any chunk);
callback WritableStreamCloseCallback = Promise<void> ();
callback WritableStreamAbortCallback = Promise<void> (optional any reason);
56 changes: 56 additions & 0 deletions reference-implementation/lib/WritableStream-impl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use strict';
const { promiseRejectedWith } = require('./webidl-helpers.js');

const { ExtractHighWaterMark, ExtractSizeAlgorithm } = require('./abstract-ops/queuing-strategy.js');
const aos = require('./abstract-ops/writable-streams.js');

const UnderlyingSink = require('../generated/UnderlyingSink.js');

exports.implementation = class WritableStreamImpl {
constructor(globalObject, [underlyingSink, strategy]) {
if (underlyingSink === undefined) {
underlyingSink = null;
}
const underlyingSinkDict = UnderlyingSink.convert(underlyingSink);
if ('type' in underlyingSinkDict) {
throw new RangeError('Invalid type is specified');
}

aos.InitializeWritableStream(this);

const sizeAlgorithm = ExtractSizeAlgorithm(strategy);
const highWaterMark = ExtractHighWaterMark(strategy, 1);

aos.SetUpWritableStreamDefaultControllerFromUnderlyingSink(
this, underlyingSink, underlyingSinkDict, highWaterMark, sizeAlgorithm
);
}

get locked() {
return aos.IsWritableStreamLocked(this);
}

abort(reason) {
if (aos.IsWritableStreamLocked(this) === true) {
return promiseRejectedWith(new TypeError('Cannot abort a stream that already has a writer'));
}

return aos.WritableStreamAbort(this, reason);
}

close() {
if (aos.IsWritableStreamLocked(this) === true) {
return promiseRejectedWith(new TypeError('Cannot close a stream that already has a writer'));
}

if (aos.WritableStreamCloseQueuedOrInFlight(this) === true) {
return promiseRejectedWith(new TypeError('Cannot close an already-closing stream'));
}

return aos.WritableStreamClose(this);
}

getWriter() {
return aos.AcquireWritableStreamDefaultWriter(this);
}
};
10 changes: 10 additions & 0 deletions reference-implementation/lib/WritableStream.webidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Exposed=(Window,Worker,Worklet)]
interface WritableStream {
constructor(optional object underlyingSink, optional QueuingStrategy strategy = {});

readonly attribute boolean locked;

Promise<void> abort(optional any reason);
Promise<void> close();
WritableStreamDefaultWriter getWriter();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';
const aos = require('./abstract-ops/writable-streams.js');
const { AbortSteps, ErrorSteps } = require('./abstract-ops/internal-methods.js');
const { ResetQueue } = require('./abstract-ops/queue-with-sizes.js');

exports.implementation = class WritableStreamDefaultControllerImpl {
error(e) {
const state = this._controlledWritableStream._state;

if (state !== 'writable') {
// The stream is closed, errored or will be soon. The sink can't do anything useful if it gets an error here, so
// just treat it as a no-op.
return;
}

aos.WritableStreamDefaultControllerError(this, e);
}

[AbortSteps](reason) {
const result = this._abortAlgorithm(reason);
aos.WritableStreamDefaultControllerClearAlgorithms(this);
return result;
}

[ErrorSteps]() {
ResetQueue(this);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[Exposed=(Window,Worker,Worklet)]
interface WritableStreamDefaultController {
void error(optional any e);
};
74 changes: 74 additions & 0 deletions reference-implementation/lib/WritableStreamDefaultWriter-impl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';
const assert = require('assert');

const { promiseRejectedWith } = require('./webidl-helpers.js');

const aos = require('./abstract-ops/writable-streams.js');

exports.implementation = class WritableStreamDefaultWriterImpl {
constructor(globalObject, [stream]) {
aos.SetUpWritableStreamDefaultWriter(this, stream);
}

get closed() {
return this._closedPromise;
}

get desiredSize() {
if (this._ownerWritableStream === undefined) {
throw defaultWriterLockException('desiredSize');
}

return aos.WritableStreamDefaultWriterGetDesiredSize(this);
}

get ready() {
return this._readyPromise;
}

abort(reason) {
if (this._ownerWritableStream === undefined) {
return promiseRejectedWith(defaultWriterLockException('abort'));
}

return aos.WritableStreamDefaultWriterAbort(this, reason);
}

close() {
const stream = this._ownerWritableStream;

if (stream === undefined) {
return promiseRejectedWith(defaultWriterLockException('close'));
}

if (aos.WritableStreamCloseQueuedOrInFlight(stream) === true) {
return promiseRejectedWith(new TypeError('Cannot close an already-closing stream'));
}

return aos.WritableStreamDefaultWriterClose(this);
}

releaseLock() {
const stream = this._ownerWritableStream;

if (stream === undefined) {
return;
}

assert(stream._writer !== undefined);

aos.WritableStreamDefaultWriterRelease(this);
}

write(chunk) {
if (this._ownerWritableStream === undefined) {
return promiseRejectedWith(defaultWriterLockException('write to'));
}

return aos.WritableStreamDefaultWriterWrite(this, chunk);
}
};

function defaultWriterLockException(name) {
return new TypeError('Cannot ' + name + ' a stream using a released writer');
}
13 changes: 13 additions & 0 deletions reference-implementation/lib/WritableStreamDefaultWriter.webidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[Exposed=(Window,Worker,Worklet)]
interface WritableStreamDefaultWriter {
constructor(WritableStream stream);

readonly attribute Promise<void> closed;
readonly attribute unrestricted double? desiredSize;
readonly attribute Promise<void> ready;

Promise<void> abort(optional any reason);
Promise<void> close();
void releaseLock();
Promise<void> write(optional any chunk);
};
4 changes: 4 additions & 0 deletions reference-implementation/lib/abstract-ops/internal-methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
'use strict';

exports.AbortSteps = Symbol('[[AbortSteps]]');
exports.ErrorSteps = Symbol('[[ErrorSteps]]');
17 changes: 17 additions & 0 deletions reference-implementation/lib/abstract-ops/miscellaneous.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

exports.IsNonNegativeNumber = v => {
if (typeof v !== 'number') {
return false;
}

if (Number.isNaN(v)) {
return false;
}

if (v < 0) {
return false;
}

return true;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';
const assert = require('assert');
const { IsFiniteNonNegativeNumber } = require('./helpers.js');
const { IsNonNegativeNumber } = require('./miscellaneous.js');

exports.DequeueValue = container => {
assert('_queue' in container && '_queueTotalSize' in container);
Expand All @@ -18,8 +18,10 @@ exports.DequeueValue = container => {
exports.EnqueueValueWithSize = (container, value, size) => {
assert('_queue' in container && '_queueTotalSize' in container);

size = Number(size);
if (!IsFiniteNonNegativeNumber(size)) {
if (!IsNonNegativeNumber(size)) {
throw new RangeError('Size must be a finite, non-NaN, non-negative number.');
}
if (size === Infinity) {
throw new RangeError('Size must be a finite, non-NaN, non-negative number.');
}

Expand Down
26 changes: 26 additions & 0 deletions reference-implementation/lib/abstract-ops/queuing-strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';
const { invoke } = require('../webidl-helpers.js');

exports.ExtractHighWaterMark = (strategy, defaultHWM) => {
if (!('highWaterMark' in strategy)) {
return defaultHWM;
}

const { highWaterMark } = strategy;
if (Number.isNaN(highWaterMark) || highWaterMark < 0) {
throw new RangeError('Invalid highWaterMark');
}

return highWaterMark;
};

exports.ExtractSizeAlgorithm = strategy => {
if (!('size' in strategy)) {
return () => 1;
}

return chunk => {
// TODO: manual number conversion won't be necessary when https://github.com/jsdom/webidl2js/pull/123 lands.
return Number(invoke(strategy.size, [chunk]));
};
};
Loading

0 comments on commit 504f25d

Please sign in to comment.