diff options
Diffstat (limited to 'Source/WebCore/Modules/streams')
20 files changed, 2309 insertions, 0 deletions
diff --git a/Source/WebCore/Modules/streams/ByteLengthQueuingStrategy.idl b/Source/WebCore/Modules/streams/ByteLengthQueuingStrategy.idl new file mode 100644 index 000000000..ac6ba9495 --- /dev/null +++ b/Source/WebCore/Modules/streams/ByteLengthQueuingStrategy.idl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 Canon Inc. + * Copyright (C) 2015 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions + * are required to be met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Canon Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +[ + JSBuiltin, + Constructor, + Conditional=READABLE_STREAM_API|WRITABLE_STREAM_API, + Exposed=(Window,Worker), +] interface ByteLengthQueuingStrategy { + double size(); +}; diff --git a/Source/WebCore/Modules/streams/ByteLengthQueuingStrategy.js b/Source/WebCore/Modules/streams/ByteLengthQueuingStrategy.js new file mode 100644 index 000000000..3dd793700 --- /dev/null +++ b/Source/WebCore/Modules/streams/ByteLengthQueuingStrategy.js @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 Canon Inc. + * Copyright (C) 2015 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// @conditional=ENABLE(READABLE_STREAM_API) || ENABLE(WRITABLE_STREAM_API) + +function size(chunk) +{ + "use strict"; + + return chunk.byteLength; +} + +function initializeByteLengthQueuingStrategy(parameters) +{ + "use strict"; + + @Object.@defineProperty(this, "highWaterMark", { + value: parameters.highWaterMark, + configurable: true, + enumerable: true, + writable: true + }); +} diff --git a/Source/WebCore/Modules/streams/CountQueuingStrategy.idl b/Source/WebCore/Modules/streams/CountQueuingStrategy.idl new file mode 100644 index 000000000..3eee7a284 --- /dev/null +++ b/Source/WebCore/Modules/streams/CountQueuingStrategy.idl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 Canon Inc. + * Copyright (C) 2015 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions + * are required to be met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Canon Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +[ + JSBuiltin, + Constructor, + Conditional=READABLE_STREAM_API|WRITABLE_STREAM_API, + Exposed=(Window,Worker), +] interface CountQueuingStrategy { + double size(); +}; diff --git a/Source/WebCore/Modules/streams/CountQueuingStrategy.js b/Source/WebCore/Modules/streams/CountQueuingStrategy.js new file mode 100644 index 000000000..ab6697836 --- /dev/null +++ b/Source/WebCore/Modules/streams/CountQueuingStrategy.js @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 Canon Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// @conditional=ENABLE(READABLE_STREAM_API) || ENABLE(WRITABLE_STREAM_API) + +function size() +{ + "use strict"; + + return 1; +} + +function initializeCountQueuingStrategy(parameters) +{ + "use strict"; + + @Object.@defineProperty(this, "highWaterMark", { + value: parameters.highWaterMark, + configurable: true, + enumerable: true, + writable: true + }); +} diff --git a/Source/WebCore/Modules/streams/ReadableByteStreamController.idl b/Source/WebCore/Modules/streams/ReadableByteStreamController.idl new file mode 100644 index 000000000..d0dc23f01 --- /dev/null +++ b/Source/WebCore/Modules/streams/ReadableByteStreamController.idl @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 Canon Inc. + * Copyright (C) 2015 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions + * are required to be met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Canon Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +[ + Conditional=READABLE_STREAM_API&READABLE_BYTE_STREAM_API, + CustomConstructor(ReadableStream stream, any underlyingByteSource, unsigned long highWaterMark), + Exposed=(Window,Worker), + JSBuiltin, + NoInterfaceObject +] interface ReadableByteStreamController { + [NotEnumerable] void enqueue(optional any chunk); + [NotEnumerable] void close(); + [NotEnumerable] void error(optional any error); + + //FIXME: Add byobRequest when implemented. + [NotEnumerable] readonly attribute double desiredSize; +}; diff --git a/Source/WebCore/Modules/streams/ReadableByteStreamController.js b/Source/WebCore/Modules/streams/ReadableByteStreamController.js new file mode 100644 index 000000000..c2feb1579 --- /dev/null +++ b/Source/WebCore/Modules/streams/ReadableByteStreamController.js @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 Canon Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// @conditional=ENABLE(READABLE_STREAM_API) && ENABLE(READABLE_BYTE_STREAM_API) + +function enqueue(chunk) +{ + "use strict"; + + if (!@isReadableByteStreamController(this)) + throw @makeThisTypeError("ReadableByteStreamController", "enqueue"); + + if (this.@closeRequested) + @throwTypeError("ReadableByteStreamController is requested to close"); + + if (this.@controlledReadableStream.@state !== @streamReadable) + @throwTypeError("ReadableStream is not readable"); + + if (!@isObject(chunk)) + @throwTypeError("Provided chunk is not an object"); + + if (!@ArrayBuffer.@isView(chunk)) + @throwTypeError("Provided chunk is not an ArrayBuffer view"); + + return @readableByteStreamControllerEnqueue(this, chunk); +} + +function error(error) +{ + "use strict"; + + if (!@isReadableByteStreamController(this)) + throw @makeThisTypeError("ReadableByteStreamController", "error"); + + if (this.@controlledReadableStream.@state !== @streamReadable) + @throwTypeError("ReadableStream is not readable"); + + @readableByteStreamControllerError(this, error); +} + +function close() +{ + "use strict"; + + if (!@isReadableByteStreamController(this)) + throw @makeThisTypeError("ReadableByteStreamController", "close"); + + if (this.@closeRequested) + @throwTypeError("Close has already been requested"); + + if (this.@controlledReadableStream.@state !== @streamReadable) + @throwTypeError("ReadableStream is not readable"); + + @readableByteStreamControllerClose(this); +} + +function byobRequest() +{ + "use strict"; + + //FIXME: Implement appropriate behavior. + @throwTypeError("ReadableByteStreamController byobRequest is not implemented"); +} + +function desiredSize() +{ + "use strict"; + + if (!@isReadableByteStreamController(this)) + throw @makeGetterTypeError("ReadableByteStreamController", "desiredSize"); + + return @readableByteStreamControllerGetDesiredSize(this); +} diff --git a/Source/WebCore/Modules/streams/ReadableByteStreamInternals.js b/Source/WebCore/Modules/streams/ReadableByteStreamInternals.js new file mode 100644 index 000000000..3e7354560 --- /dev/null +++ b/Source/WebCore/Modules/streams/ReadableByteStreamInternals.js @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2016 Canon Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// @conditional=ENABLE(READABLE_STREAM_API) && ENABLE(READABLE_BYTE_STREAM_API) +// @internal + +function privateInitializeReadableByteStreamController(stream, underlyingByteSource, highWaterMark) +{ + "use strict"; + + if (!@isReadableStream(stream)) + @throwTypeError("ReadableByteStreamController needs a ReadableStream"); + + // readableStreamController is initialized with null value. + if (stream.@readableStreamController !== null) + @throwTypeError("ReadableStream already has a controller"); + + this.@controlledReadableStream = stream; + this.@underlyingByteSource = underlyingByteSource; + this.@pullAgain = false; + this.@pulling = false; + @readableByteStreamControllerClearPendingPullIntos(this); + this.@queue = []; + this.@totalQueuedBytes = 0; + this.@started = false; + this.@closeRequested = false; + + let hwm = @Number(highWaterMark); + if (@isNaN(hwm) || hwm < 0) + @throwRangeError("highWaterMark value is negative or not a number"); + this.@strategyHWM = hwm; + + let autoAllocateChunkSize = underlyingByteSource.autoAllocateChunkSize; + if (autoAllocateChunkSize !== @undefined) { + autoAllocateChunkSize = @Number(autoAllocateChunkSize); + if (autoAllocateChunkSize <= 0 || autoAllocateChunkSize === @Number.POSITIVE_INFINITY || autoAllocateChunkSize === @Number.NEGATIVE_INFINITY) + @throwRangeError("autoAllocateChunkSize value is negative or equal to positive or negative infinity"); + } + this.@autoAllocateChunkSize = autoAllocateChunkSize; + this.@pendingPullIntos = []; + + const controller = this; + const startResult = @promiseInvokeOrNoopNoCatch(underlyingByteSource, "start", [this]).@then(() => { + controller.@started = true; + @assert(!controller.@pulling); + @assert(!controller.@pullAgain); + @readableByteStreamControllerCallPullIfNeeded(controller); + }, (error) => { + if (stream.@state === @streamReadable) + @readableByteStreamControllerError(controller, error); + }); + + this.@cancel = @readableByteStreamControllerCancel; + this.@pull = @readableByteStreamControllerPull; + + return this; +} + +function isReadableByteStreamController(controller) +{ + "use strict"; + + // Same test mechanism as in isReadableStreamDefaultController (ReadableStreamInternals.js). + // See corresponding function for explanations. + return @isObject(controller) && !!controller.@underlyingByteSource; +} + +function isReadableStreamBYOBReader(reader) +{ + "use strict"; + + // FIXME: Since BYOBReader is not yet implemented, always return false. + // To be implemented at the same time as BYOBReader (see isReadableStreamDefaultReader + // to apply same model). + return false; +} + +function readableByteStreamControllerCancel(controller, reason) +{ + "use strict"; + + if (controller.@pendingPullIntos.length > 0) + controller.@pendingPullIntos[0].bytesFilled = 0; + controller.@queue = []; + controller.@totalQueuedBytes = 0; + return @promiseInvokeOrNoop(controller.@underlyingByteSource, "cancel", [reason]); +} + +function readableByteStreamControllerError(controller, e) +{ + "use strict"; + + @assert(controller.@controlledReadableStream.@state === @streamReadable); + @readableByteStreamControllerClearPendingPullIntos(controller); + controller.@queue = []; + @readableStreamError(controller.@controlledReadableStream, e); +} + +function readableByteStreamControllerClose(controller) +{ + "use strict"; + + @assert(!controller.@closeRequested); + @assert(controller.@controlledReadableStream.@state === @streamReadable); + + if (controller.@totalQueuedBytes > 0) { + controller.@closeRequested = true; + return; + } + + if (controller.@pendingPullIntos.length > 0) { + if (controller.@pendingPullIntos[0].bytesFilled > 0) { + const e = new @TypeError("Close requested while there remain pending bytes"); + @readableByteStreamControllerError(controller, e); + throw e; + } + } + + @readableStreamClose(controller.@controlledReadableStream); +} + +function readableByteStreamControllerClearPendingPullIntos(controller) +{ + "use strict"; + + // FIXME: To be implemented in conjunction with ReadableStreamBYOBRequest. +} + +function readableByteStreamControllerGetDesiredSize(controller) +{ + "use strict"; + + return controller.@strategyHWM - controller.@totalQueuedBytes; +} + +function readableStreamHasBYOBReader(stream) +{ + "use strict"; + + return stream.@reader !== @undefined && @isReadableStreamBYOBReader(stream.@reader); +} + +function readableStreamHasDefaultReader(stream) +{ + "use strict"; + + return stream.@reader !== @undefined && @isReadableStreamDefaultReader(stream.@reader); +} + +function readableByteStreamControllerHandleQueueDrain(controller) { + + "use strict"; + + @assert(controller.@controlledReadableStream.@state === @streamReadable); + if (!controller.@totalQueuedBytes && controller.@closeRequested) + @readableStreamClose(controller.@controlledReadableStream); + else + @readableByteStreamControllerCallPullIfNeeded(controller); +} + +function readableByteStreamControllerPull(controller) +{ + "use strict"; + + const stream = controller.@controlledReadableStream; + @assert(@readableStreamHasDefaultReader(stream)); + + if (controller.@totalQueuedBytes > 0) { + @assert(stream.@reader.@readRequests.length === 0); + const entry = controller.@queue.@shift(); + controller.@totalQueuedBytes -= entry.byteLength; + @readableByteStreamControllerHandleQueueDrain(controller); + let view; + try { + view = new @Uint8Array(entry.buffer, entry.byteOffset, entry.byteLength); + } catch (error) { + return @Promise.@reject(error); + } + return @Promise.@resolve({value: view, done: false}); + } + + if (controller.@autoAllocateChunkSize !== @undefined) { + let buffer; + try { + buffer = new @ArrayBuffer(controller.@autoAllocateChunkSize); + } catch (error) { + return @Promise.@reject(error); + } + const pullIntoDescriptor = { + buffer, + byteOffset: 0, + byteLength: controller.@autoAllocateChunkSize, + bytesFilled: 0, + elementSize: 1, + ctor: @Uint8Array, + readerType: 'default' + }; + controller.@pendingPullIntos.@push(pullIntoDescriptor); + } + + const promise = @readableStreamAddReadRequest(stream); + @readableByteStreamControllerCallPullIfNeeded(controller); + return promise; +} + +function readableByteStreamControllerShouldCallPull(controller) +{ + "use strict"; + + const stream = controller.@controlledReadableStream; + + if (stream.@state !== @streamReadable) + return false; + if (controller.@closeRequested) + return false; + if (!controller.@started) + return false; + if (@readableStreamHasDefaultReader(stream) && stream.@reader.@readRequests.length > 0) + return true; + if (@readableStreamHasBYOBReader(stream) && stream.@reader.@readIntoRequests.length > 0) + return true; + if (@readableByteStreamControllerGetDesiredSize(controller) > 0) + return true; + return false; +} + +function readableByteStreamControllerCallPullIfNeeded(controller) +{ + "use strict"; + + if (!@readableByteStreamControllerShouldCallPull(controller)) + return; + + if (controller.@pulling) { + controller.@pullAgain = true; + return; + } + + @assert(!controller.@pullAgain); + controller.@pulling = true; + @promiseInvokeOrNoop(controller.@underlyingByteSource, "pull", [controller]).@then(() => { + controller.@pulling = false; + if (controller.@pullAgain) { + controller.@pullAgain = false; + @readableByteStreamControllerCallPullIfNeeded(controller); + } + }, (error) => { + if (controller.@controlledReadableStream.@state === @streamReadable) + @readableByteStreamControllerError(controller, error); + }); +} + +function transferBufferToCurrentRealm(buffer) +{ + "use strict"; + + // FIXME: Determine what should be done here exactly (what is already existing in current + // codebase and what has to be added). According to spec, Transfer operation should be + // performed in order to transfer buffer to current realm. For the moment, simply return + // received buffer. + return buffer; +} + +function readableByteStreamControllerEnqueue(controller, chunk) +{ + "use strict"; + + const stream = controller.@controlledReadableStream; + @assert(!controller.@closeRequested); + @assert(stream.@state === @streamReadable); + const buffer = chunk.buffer; + const byteOffset = chunk.byteOffset; + const byteLength = chunk.byteLength; + const transferredBuffer = @transferBufferToCurrentRealm(buffer); + + if (@readableStreamHasDefaultReader(stream)) { + if (!stream.@reader.@readRequests.length) + @readableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength); + else { + @assert(!controller.@queue.length); + let transferredView = new @Uint8Array(transferredBuffer, byteOffset, byteLength); + @readableStreamFulfillReadRequest(stream, transferredView, false); + } + return; + } + + if (@readableStreamHasBYOBReader(stream)) { + // FIXME: To be implemented once ReadableStreamBYOBReader has been implemented (for the moment, + // test cannot be true). + @throwTypeError("ReadableByteStreamController enqueue operation has no support for BYOB reader"); + return; + } + + @assert(!@isReadableStreamLocked(stream)); + @readableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength); +} + +function readableByteStreamControllerEnqueueChunkToQueue(controller, buffer, byteOffset, byteLength) +{ + "use strict"; + + controller.@queue.@push({ + buffer: buffer, + byteOffset: byteOffset, + byteLength: byteLength + }); + controller.@totalQueuedBytes += byteLength; +} diff --git a/Source/WebCore/Modules/streams/ReadableStream.idl b/Source/WebCore/Modules/streams/ReadableStream.idl new file mode 100644 index 000000000..3d841481b --- /dev/null +++ b/Source/WebCore/Modules/streams/ReadableStream.idl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 Canon Inc. + * Copyright (C) 2015 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions + * are required to be met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Canon Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +[ + Conditional=READABLE_STREAM_API, + Constructor(optional any underlyingSource, optional any options), + Exposed=(Window,Worker), + JSBuiltin, + PrivateIdentifier, + PublicIdentifier +] interface ReadableStream { + [NotEnumerable] Promise<any> cancel(optional any reason); + [NotEnumerable] Object getReader(optional any options); + [NotEnumerable] Promise<any> pipeTo(any streams, optional any options); + [NotEnumerable] Object pipeThrough(any dest, any options); + [NotEnumerable] Object tee(); + + [NotEnumerable] readonly attribute boolean locked; +}; diff --git a/Source/WebCore/Modules/streams/ReadableStream.js b/Source/WebCore/Modules/streams/ReadableStream.js new file mode 100644 index 000000000..faf6c1611 --- /dev/null +++ b/Source/WebCore/Modules/streams/ReadableStream.js @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2015 Canon Inc. + * Copyright (C) 2015 Igalia. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// @conditional=ENABLE(READABLE_STREAM_API) + +function initializeReadableStream(underlyingSource, strategy) +{ + "use strict"; + + if (underlyingSource === @undefined) + underlyingSource = { }; + if (strategy === @undefined) + strategy = { highWaterMark: 1, size: function() { return 1; } }; + + if (!@isObject(underlyingSource)) + @throwTypeError("ReadableStream constructor takes an object as first argument"); + + if (strategy !== @undefined && !@isObject(strategy)) + @throwTypeError("ReadableStream constructor takes an object as second argument, if any"); + + this.@state = @streamReadable; + this.@reader = @undefined; + this.@storedError = @undefined; + this.@disturbed = false; + // Initialized with null value to enable distinction with undefined case. + this.@readableStreamController = null; + + const type = underlyingSource.type; + const typeString = @String(type); + + if (typeString === "bytes") { + if (strategy.highWaterMark === @undefined) + strategy.highWaterMark = 0; + // FIXME: When ReadableByteStreamController is no more dependent on a compile flag, specific error handling can be removed. + // Constructor is not necessarily available if the byteStream part of Readeable Stream API is not activated. Therefore, a + // specific handling of error is done. + try { + let readableByteStreamControllerConstructor = @ReadableByteStreamController; + } catch (e) { + @throwTypeError("ReadableByteStreamController is not implemented"); + } + this.@readableStreamController = new @ReadableByteStreamController(this, underlyingSource, strategy.highWaterMark); + } else if (type === @undefined) { + if (strategy.highWaterMark === @undefined) + strategy.highWaterMark = 1; + this.@readableStreamController = new @ReadableStreamDefaultController(this, underlyingSource, strategy.size, strategy.highWaterMark); + } else + @throwRangeError("Invalid type for underlying source"); + + return this; +} + +function cancel(reason) +{ + "use strict"; + + if (!@isReadableStream(this)) + return @Promise.@reject(@makeThisTypeError("ReadableStream", "cancel")); + + if (@isReadableStreamLocked(this)) + return @Promise.@reject(new @TypeError("ReadableStream is locked")); + + return @readableStreamCancel(this, reason); +} + +function getReader(options) +{ + "use strict"; + + if (!@isReadableStream(this)) + throw @makeThisTypeError("ReadableStream", "getReader"); + + if (options === @undefined) + options = { }; + + if (options.mode === 'byob') { + // FIXME: Update once ReadableByteStreamContoller and ReadableStreamBYOBReader are implemented. + @throwTypeError("ReadableStreamBYOBReader is not implemented"); + } + + if (options.mode === @undefined) + return new @ReadableStreamDefaultReader(this); + + @throwRangeError("Invalid mode is specified"); +} + +function pipeThrough(streams, options) +{ + "use strict"; + + const writable = streams.writable; + const readable = streams.readable; + this.pipeTo(writable, options); + return readable; +} + +function pipeTo(destination) +{ + "use strict"; + + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=159869. + // Built-in generator should be able to parse function signature to compute the function length correctly. + const options = arguments[1]; + + // FIXME: rewrite pipeTo so as to require to have 'this' as a ReadableStream and destination be a WritableStream. + // See https://github.com/whatwg/streams/issues/407. + // We should shield the pipeTo implementation at the same time. + + const preventClose = @isObject(options) && !!options.preventClose; + const preventAbort = @isObject(options) && !!options.preventAbort; + const preventCancel = @isObject(options) && !!options.preventCancel; + + const source = this; + + let reader; + let lastRead; + let lastWrite; + let closedPurposefully = false; + let promiseCapability; + + function doPipe() { + lastRead = reader.read(); + @Promise.prototype.@then.@call(@Promise.all([lastRead, destination.ready]), function([{ value, done }]) { + if (done) + closeDestination(); + else if (destination.state === "writable") { + lastWrite = destination.write(value); + doPipe(); + } + }, function(e) { + throw e; + }); + } + + function cancelSource(reason) { + if (!preventCancel) { + reader.cancel(reason); + reader.releaseLock(); + promiseCapability.@reject.@call(@undefined, reason); + } else { + @Promise.prototype.@then.@call(lastRead, function() { + reader.releaseLock(); + promiseCapability.@reject.@call(@undefined, reason); + }); + } + } + + function closeDestination() { + reader.releaseLock(); + + const destinationState = destination.state; + if (!preventClose && (destinationState === "waiting" || destinationState === "writable")) { + closedPurposefully = true; + @Promise.prototype.@then.@call(destination.close(), promiseCapability.@resolve, promiseCapability.@reject); + } else if (lastWrite !== @undefined) + @Promise.prototype.@then.@call(lastWrite, promiseCapability.@resolve, promiseCapability.@reject); + else + promiseCapability.@resolve.@call(); + + } + + function abortDestination(reason) { + reader.releaseLock(); + + if (!preventAbort) + destination.abort(reason); + promiseCapability.@reject.@call(@undefined, reason); + } + + promiseCapability = @newPromiseCapability(@Promise); + + reader = source.getReader(); + + @Promise.prototype.@then.@call(reader.closed, @undefined, abortDestination); + @Promise.prototype.@then.@call(destination.closed, + function() { + if (!closedPurposefully) + cancelSource(new @TypeError('destination is closing or closed and cannot be piped to anymore')); + }, + cancelSource + ); + + doPipe(); + + return promiseCapability.@promise; +} + +function tee() +{ + "use strict"; + + if (!@isReadableStream(this)) + throw @makeThisTypeError("ReadableStream", "tee"); + + return @readableStreamTee(this, false); +} + +function locked() +{ + "use strict"; + + if (!@isReadableStream(this)) + throw @makeGetterTypeError("ReadableStream", "locked"); + + return @isReadableStreamLocked(this); +} diff --git a/Source/WebCore/Modules/streams/ReadableStreamDefaultController.idl b/Source/WebCore/Modules/streams/ReadableStreamDefaultController.idl new file mode 100644 index 000000000..9d05c952a --- /dev/null +++ b/Source/WebCore/Modules/streams/ReadableStreamDefaultController.idl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 Canon Inc. + * Copyright (C) 2015 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions + * are required to be met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Canon Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +[ + Conditional=READABLE_STREAM_API, + CustomConstructor(ReadableStream stream, any underlyingSource, unsigned long size, unsigned long highWaterMark), + Exposed=(Window,Worker), + JSBuiltin, + NoInterfaceObject +] interface ReadableStreamDefaultController { + [NotEnumerable] void enqueue(optional any chunk); + [NotEnumerable] void close(); + [NotEnumerable] void error(optional any error); + + [NotEnumerable] readonly attribute double desiredSize; +}; diff --git a/Source/WebCore/Modules/streams/ReadableStreamDefaultController.js b/Source/WebCore/Modules/streams/ReadableStreamDefaultController.js new file mode 100644 index 000000000..e9c94998a --- /dev/null +++ b/Source/WebCore/Modules/streams/ReadableStreamDefaultController.js @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015 Canon Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// @conditional=ENABLE(READABLE_STREAM_API) + +function enqueue(chunk) +{ + "use strict"; + + if (!@isReadableStreamDefaultController(this)) + throw @makeThisTypeError("ReadableStreamDefaultController", "enqueue"); + + if (this.@closeRequested) + @throwTypeError("ReadableStreamDefaultController is requested to close"); + + if (this.@controlledReadableStream.@state !== @streamReadable) + @throwTypeError("ReadableStream is not readable"); + + return @readableStreamDefaultControllerEnqueue(this, chunk); +} + +function error(error) +{ + "use strict"; + + if (!@isReadableStreamDefaultController(this)) + throw @makeThisTypeError("ReadableStreamDefaultController", "error"); + + if (this.@controlledReadableStream.@state !== @streamReadable) + @throwTypeError("ReadableStream is not readable"); + + @readableStreamDefaultControllerError(this, error); +} + +function close() +{ + "use strict"; + + if (!@isReadableStreamDefaultController(this)) + throw @makeThisTypeError("ReadableStreamDefaultController", "close"); + + if (this.@closeRequested) + @throwTypeError("ReadableStreamDefaultController is already requested to close"); + + if (this.@controlledReadableStream.@state !== @streamReadable) + @throwTypeError("ReadableStream is not readable"); + + @readableStreamDefaultControllerClose(this); +} + +function desiredSize() +{ + "use strict"; + + if (!@isReadableStreamDefaultController(this)) + throw @makeGetterTypeError("ReadableStreamDefaultController", "desiredSize"); + + return @readableStreamDefaultControllerGetDesiredSize(this); +} + diff --git a/Source/WebCore/Modules/streams/ReadableStreamDefaultReader.idl b/Source/WebCore/Modules/streams/ReadableStreamDefaultReader.idl new file mode 100644 index 000000000..fd2926fc4 --- /dev/null +++ b/Source/WebCore/Modules/streams/ReadableStreamDefaultReader.idl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 Canon Inc. + * Copyright (C) 2015 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions + * are required to be met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Canon Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +[ + Conditional=READABLE_STREAM_API, + CustomConstructor(ReadableStream stream), + Exposed=(Window,Worker), + JSBuiltin, + NoInterfaceObject +] interface ReadableStreamDefaultReader { + [NotEnumerable] Promise<any> read(); + [NotEnumerable] Promise<any> cancel(optional any reason); + [NotEnumerable] void releaseLock(); + + [NotEnumerable] readonly attribute Promise<boolean> closed; +}; diff --git a/Source/WebCore/Modules/streams/ReadableStreamDefaultReader.js b/Source/WebCore/Modules/streams/ReadableStreamDefaultReader.js new file mode 100644 index 000000000..d5a51c3a0 --- /dev/null +++ b/Source/WebCore/Modules/streams/ReadableStreamDefaultReader.js @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2015 Canon Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// @conditional=ENABLE(READABLE_STREAM_API) + +function cancel(reason) +{ + "use strict"; + + if (!@isReadableStreamDefaultReader(this)) + return @Promise.@reject(@makeThisTypeError("ReadableStreamDefaultReader", "cancel")); + + if (!this.@ownerReadableStream) + return @Promise.@reject(new @TypeError("cancel() called on a reader owned by no readable stream")); + + return @readableStreamReaderGenericCancel(this, reason); +} + +function read() +{ + "use strict"; + + if (!@isReadableStreamDefaultReader(this)) + return @Promise.@reject(@makeThisTypeError("ReadableStreamDefaultReader", "read")); + if (!this.@ownerReadableStream) + return @Promise.@reject(new @TypeError("read() called on a reader owned by no readable stream")); + + return @readableStreamDefaultReaderRead(this); +} + +function releaseLock() +{ + "use strict"; + + if (!@isReadableStreamDefaultReader(this)) + throw @makeThisTypeError("ReadableStreamDefaultReader", "releaseLock"); + + if (!this.@ownerReadableStream) + return; + + if (this.@readRequests.length) + @throwTypeError("There are still pending read requests, cannot release the lock"); + + @readableStreamReaderGenericRelease(this); +} + +function closed() +{ + "use strict"; + + if (!@isReadableStreamDefaultReader(this)) + return @Promise.@reject(@makeGetterTypeError("ReadableStreamDefaultReader", "closed")); + + return this.@closedPromiseCapability.@promise; +} diff --git a/Source/WebCore/Modules/streams/ReadableStreamInternals.js b/Source/WebCore/Modules/streams/ReadableStreamInternals.js new file mode 100644 index 000000000..87094bf6a --- /dev/null +++ b/Source/WebCore/Modules/streams/ReadableStreamInternals.js @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2015 Canon Inc. All rights reserved. + * Copyright (C) 2015 Igalia. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// @conditional=ENABLE(READABLE_STREAM_API) +// @internal + +function privateInitializeReadableStreamDefaultReader(stream) +{ + "use strict"; + + if (!@isReadableStream(stream)) + @throwTypeError("ReadableStreamDefaultReader needs a ReadableStream"); + if (@isReadableStreamLocked(stream)) + @throwTypeError("ReadableStream is locked"); + + @readableStreamReaderGenericInitialize(this, stream); + this.@readRequests = []; + + return this; +} + +function readableStreamReaderGenericInitialize(reader, stream) +{ + "use strict"; + + reader.@ownerReadableStream = stream; + stream.@reader = reader; + if (stream.@state === @streamReadable) + reader.@closedPromiseCapability = @newPromiseCapability(@Promise); + else if (stream.@state === @streamClosed) + reader.@closedPromiseCapability = { @promise: @Promise.@resolve() }; + else { + @assert(stream.@state === @streamErrored); + reader.@closedPromiseCapability = { @promise: @Promise.@reject(stream.@storedError) }; + } +} + +function privateInitializeReadableStreamDefaultController(stream, underlyingSource, size, highWaterMark) +{ + "use strict"; + + if (!@isReadableStream(stream)) + @throwTypeError("ReadableStreamDefaultController needs a ReadableStream"); + + // readableStreamController is initialized with null value. + if (stream.@readableStreamController !== null) + @throwTypeError("ReadableStream already has a controller"); + + this.@controlledReadableStream = stream; + this.@underlyingSource = underlyingSource; + this.@queue = @newQueue(); + this.@started = false; + this.@closeRequested = false; + this.@pullAgain = false; + this.@pulling = false; + this.@strategy = @validateAndNormalizeQueuingStrategy(size, highWaterMark); + + const controller = this; + @promiseInvokeOrNoopNoCatch(underlyingSource, "start", [this]).@then(() => { + controller.@started = true; + @assert(!controller.@pulling); + @assert(!controller.@pullAgain); + @readableStreamDefaultControllerCallPullIfNeeded(controller); + }, (error) => { + if (stream.@state === @streamReadable) + @readableStreamDefaultControllerError(controller, error); + }); + + this.@cancel = @readableStreamDefaultControllerCancel; + + this.@pull = @readableStreamDefaultControllerPull; + + return this; +} + +function readableStreamDefaultControllerError(controller, error) +{ + "use strict"; + + const stream = controller.@controlledReadableStream; + @assert(stream.@state === @streamReadable); + controller.@queue = @newQueue(); + @readableStreamError(stream, error); +} + +function readableStreamTee(stream, shouldClone) +{ + "use strict"; + + @assert(@isReadableStream(stream)); + @assert(typeof(shouldClone) === "boolean"); + + const reader = new @ReadableStreamDefaultReader(stream); + + const teeState = { + closedOrErrored: false, + canceled1: false, + canceled2: false, + reason1: @undefined, + reason2: @undefined, + }; + + teeState.cancelPromiseCapability = @newPromiseCapability(@InternalPromise); + + const pullFunction = @readableStreamTeePullFunction(teeState, reader, shouldClone); + + const branch1 = new @ReadableStream({ + "pull": pullFunction, + "cancel": @readableStreamTeeBranch1CancelFunction(teeState, stream) + }); + const branch2 = new @ReadableStream({ + "pull": pullFunction, + "cancel": @readableStreamTeeBranch2CancelFunction(teeState, stream) + }); + + reader.@closedPromiseCapability.@promise.@then(@undefined, function(e) { + if (teeState.closedOrErrored) + return; + @readableStreamDefaultControllerError(branch1.@readableStreamController, e); + @readableStreamDefaultControllerError(branch2.@readableStreamController, e); + teeState.closedOrErrored = true; + }); + + // Additional fields compared to the spec, as they are needed within pull/cancel functions. + teeState.branch1 = branch1; + teeState.branch2 = branch2; + + return [branch1, branch2]; +} + +function doStructuredClone(object) +{ + "use strict"; + + // FIXME: We should implement http://w3c.github.io/html/infrastructure.html#ref-for-structured-clone-4 + // Implementation is currently limited to ArrayBuffer/ArrayBufferView to meet Fetch API needs. + + if (object instanceof @ArrayBuffer) + return @structuredCloneArrayBuffer(object); + + if (@ArrayBuffer.@isView(object)) + return @structuredCloneArrayBufferView(object); + + @throwTypeError("structuredClone not implemented for: " + object); +} + +function readableStreamTeePullFunction(teeState, reader, shouldClone) +{ + "use strict"; + + return function() { + @Promise.prototype.@then.@call(@readableStreamDefaultReaderRead(reader), function(result) { + @assert(@isObject(result)); + @assert(typeof result.done === "boolean"); + if (result.done && !teeState.closedOrErrored) { + if (!teeState.canceled1) + @readableStreamDefaultControllerClose(teeState.branch1.@readableStreamController); + if (!teeState.canceled2) + @readableStreamDefaultControllerClose(teeState.branch2.@readableStreamController); + teeState.closedOrErrored = true; + } + if (teeState.closedOrErrored) + return; + if (!teeState.canceled1) + @readableStreamDefaultControllerEnqueue(teeState.branch1.@readableStreamController, result.value); + if (!teeState.canceled2) + @readableStreamDefaultControllerEnqueue(teeState.branch2.@readableStreamController, shouldClone ? @doStructuredClone(result.value) : result.value); + }); + } +} + +function readableStreamTeeBranch1CancelFunction(teeState, stream) +{ + "use strict"; + + return function(r) { + teeState.canceled1 = true; + teeState.reason1 = r; + if (teeState.canceled2) { + @readableStreamCancel(stream, [teeState.reason1, teeState.reason2]).@then( + teeState.cancelPromiseCapability.@resolve, + teeState.cancelPromiseCapability.@reject); + } + return teeState.cancelPromiseCapability.@promise; + } +} + +function readableStreamTeeBranch2CancelFunction(teeState, stream) +{ + "use strict"; + + return function(r) { + teeState.canceled2 = true; + teeState.reason2 = r; + if (teeState.canceled1) { + @readableStreamCancel(stream, [teeState.reason1, teeState.reason2]).@then( + teeState.cancelPromiseCapability.@resolve, + teeState.cancelPromiseCapability.@reject); + } + return teeState.cancelPromiseCapability.@promise; + } +} + +function isReadableStream(stream) +{ + "use strict"; + + // Spec tells to return true only if stream has a readableStreamController internal slot. + // However, since it is a private slot, it cannot be checked using hasOwnProperty(). + // Therefore, readableStreamController is initialized with null value. + return @isObject(stream) && stream.@readableStreamController !== @undefined; +} + +function isReadableStreamDefaultReader(reader) +{ + "use strict"; + + // Spec tells to return true only if reader has a readRequests internal slot. + // However, since it is a private slot, it cannot be checked using hasOwnProperty(). + // Since readRequests is initialized with an empty array, the following test is ok. + return @isObject(reader) && !!reader.@readRequests; +} + +function isReadableStreamDefaultController(controller) +{ + "use strict"; + + // Spec tells to return true only if controller has an underlyingSource internal slot. + // However, since it is a private slot, it cannot be checked using hasOwnProperty(). + // underlyingSource is obtained in ReadableStream constructor: if undefined, it is set + // to an empty object. Therefore, following test is ok. + return @isObject(controller) && !!controller.@underlyingSource; +} + +function readableStreamError(stream, error) +{ + "use strict"; + + @assert(@isReadableStream(stream)); + @assert(stream.@state === @streamReadable); + stream.@state = @streamErrored; + stream.@storedError = error; + + if (!stream.@reader) + return; + + const reader = stream.@reader; + + if (@isReadableStreamDefaultReader(reader)) { + const requests = reader.@readRequests; + for (let index = 0, length = requests.length; index < length; ++index) + requests[index].@reject.@call(@undefined, error); + reader.@readRequests = []; + } else + // FIXME: Implement ReadableStreamBYOBReader. + @throwTypeError("Only ReadableStreamDefaultReader is currently supported"); + + reader.@closedPromiseCapability.@reject.@call(@undefined, error); +} + +function readableStreamDefaultControllerCallPullIfNeeded(controller) +{ + "use strict"; + + const stream = controller.@controlledReadableStream; + + if (stream.@state === @streamClosed || stream.@state === @streamErrored) + return; + if (controller.@closeRequested) + return; + if (!controller.@started) + return; + if ((!@isReadableStreamLocked(stream) || !stream.@reader.@readRequests.length) && @readableStreamDefaultControllerGetDesiredSize(controller) <= 0) + return; + + if (controller.@pulling) { + controller.@pullAgain = true; + return; + } + + @assert(!controller.@pullAgain); + controller.@pulling = true; + + @promiseInvokeOrNoop(controller.@underlyingSource, "pull", [controller]).@then(function() { + controller.@pulling = false; + if (controller.@pullAgain) { + controller.@pullAgain = false; + @readableStreamDefaultControllerCallPullIfNeeded(controller); + } + }, function(error) { + if (stream.@state === @streamReadable) + @readableStreamDefaultControllerError(controller, error); + }); +} + +function isReadableStreamLocked(stream) +{ + "use strict"; + + @assert(@isReadableStream(stream)); + return !!stream.@reader; +} + +function readableStreamDefaultControllerGetDesiredSize(controller) +{ + "use strict"; + + return controller.@strategy.highWaterMark - controller.@queue.size; +} + + +function readableStreamReaderGenericCancel(reader, reason) +{ + "use strict"; + + const stream = reader.@ownerReadableStream; + @assert(!!stream); + return @readableStreamCancel(stream, reason); +} + +function readableStreamCancel(stream, reason) +{ + "use strict"; + + stream.@disturbed = true; + if (stream.@state === @streamClosed) + return @Promise.@resolve(); + if (stream.@state === @streamErrored) + return @Promise.@reject(stream.@storedError); + @readableStreamClose(stream); + return stream.@readableStreamController.@cancel(stream.@readableStreamController, reason).@then(function() { }); +} + +function readableStreamDefaultControllerCancel(controller, reason) +{ + "use strict"; + + controller.@queue = @newQueue(); + return @promiseInvokeOrNoop(controller.@underlyingSource, "cancel", [reason]); +} + +function readableStreamDefaultControllerPull(controller) +{ + "use strict"; + + const stream = controller.@controlledReadableStream; + if (controller.@queue.content.length) { + const chunk = @dequeueValue(controller.@queue); + if (controller.@closeRequested && controller.@queue.content.length === 0) + @readableStreamClose(stream); + else + @readableStreamDefaultControllerCallPullIfNeeded(controller); + return @Promise.@resolve({value: chunk, done: false}); + } + const pendingPromise = @readableStreamAddReadRequest(stream); + @readableStreamDefaultControllerCallPullIfNeeded(controller); + return pendingPromise; +} + +function readableStreamDefaultControllerClose(controller) +{ + "use strict"; + + const stream = controller.@controlledReadableStream; + @assert(!controller.@closeRequested); + @assert(stream.@state === @streamReadable); + controller.@closeRequested = true; + if (controller.@queue.content.length === 0) + @readableStreamClose(stream); +} + +function readableStreamClose(stream) +{ + "use strict"; + + @assert(stream.@state === @streamReadable); + stream.@state = @streamClosed; + const reader = stream.@reader; + + if (!reader) + return; + + if (@isReadableStreamDefaultReader(reader)) { + const requests = reader.@readRequests; + for (let index = 0, length = requests.length; index < length; ++index) + requests[index].@resolve.@call(@undefined, {value:@undefined, done: true}); + reader.@readRequests = []; + } + + reader.@closedPromiseCapability.@resolve.@call(); +} + +function readableStreamFulfillReadRequest(stream, chunk, done) +{ + "use strict"; + + stream.@reader.@readRequests.@shift().@resolve.@call(@undefined, {value: chunk, done: done}); +} + +function readableStreamDefaultControllerEnqueue(controller, chunk) +{ + "use strict"; + + const stream = controller.@controlledReadableStream; + @assert(!controller.@closeRequested); + @assert(stream.@state === @streamReadable); + + if (@isReadableStreamLocked(stream) && stream.@reader.@readRequests.length) { + @readableStreamFulfillReadRequest(stream, chunk, false); + @readableStreamDefaultControllerCallPullIfNeeded(controller); + return; + } + + try { + let chunkSize = 1; + if (controller.@strategy.size !== @undefined) + chunkSize = controller.@strategy.size(chunk); + @enqueueValueWithSize(controller.@queue, chunk, chunkSize); + } + catch(error) { + if (stream.@state === @streamReadable) + @readableStreamDefaultControllerError(controller, error); + throw error; + } + @readableStreamDefaultControllerCallPullIfNeeded(controller); +} + +function readableStreamDefaultReaderRead(reader) +{ + "use strict"; + + const stream = reader.@ownerReadableStream; + @assert(!!stream); + + stream.@disturbed = true; + if (stream.@state === @streamClosed) + return @Promise.@resolve({value: @undefined, done: true}); + if (stream.@state === @streamErrored) + return @Promise.@reject(stream.@storedError); + @assert(stream.@state === @streamReadable); + + return stream.@readableStreamController.@pull(stream.@readableStreamController); +} + +function readableStreamAddReadRequest(stream) +{ + "use strict"; + + @assert(@isReadableStreamDefaultReader(stream.@reader)); + @assert(stream.@state == @streamReadable); + + const readRequest = @newPromiseCapability(@Promise); + stream.@reader.@readRequests.@push(readRequest); + + return readRequest.@promise; +} + +function isReadableStreamDisturbed(stream) +{ + "use strict"; + + @assert(@isReadableStream(stream)); + return stream.@disturbed; +} + +function readableStreamReaderGenericRelease(reader) +{ + "use strict"; + + @assert(!!reader.@ownerReadableStream); + @assert(reader.@ownerReadableStream.@reader === reader); + + if (reader.@ownerReadableStream.@state === @streamReadable) + reader.@closedPromiseCapability.@reject.@call(@undefined, new @TypeError("releasing lock of reader whose stream is still in readable state")); + else + reader.@closedPromiseCapability = { @promise: @Promise.@reject(new @TypeError("reader released lock")) }; + + reader.@ownerReadableStream.@reader = @undefined; + reader.@ownerReadableStream = null; +} diff --git a/Source/WebCore/Modules/streams/ReadableStreamSource.h b/Source/WebCore/Modules/streams/ReadableStreamSource.h new file mode 100644 index 000000000..65919f294 --- /dev/null +++ b/Source/WebCore/Modules/streams/ReadableStreamSource.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016 Canon Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions + * are required to be met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Canon Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(READABLE_STREAM_API) + +#include "JSDOMPromise.h" +#include "ReadableStreamDefaultController.h" +#include <wtf/Optional.h> + +namespace WebCore { + +class ReadableStreamSource : public RefCounted<ReadableStreamSource> { +public: + virtual ~ReadableStreamSource() { } + + void start(ReadableStreamDefaultController&&, DOMPromise<void>&&); + void pull(DOMPromise<void>&&); + void cancel(JSC::JSValue); + + bool isPulling() const { return !!m_promise; } + +protected: + ReadableStreamDefaultController& controller() { return m_controller.value(); } + const ReadableStreamDefaultController& controller() const { return m_controller.value(); } + + void startFinished(); + void pullFinished(); + void cancelFinished(); + void clean(); + + virtual void setActive() = 0; + virtual void setInactive() = 0; + + virtual void doStart() = 0; + virtual void doPull() = 0; + virtual void doCancel() = 0; + +private: + std::optional<DOMPromise<void>> m_promise; + std::optional<ReadableStreamDefaultController> m_controller; +}; + +inline void ReadableStreamSource::start(ReadableStreamDefaultController&& controller, DOMPromise<void>&& promise) +{ + ASSERT(!m_promise); + m_promise = WTFMove(promise); + m_controller = WTFMove(controller); + + setActive(); + doStart(); +} + +inline void ReadableStreamSource::pull(DOMPromise<void>&& promise) +{ + ASSERT(!m_promise); + ASSERT(m_controller); + + m_promise = WTFMove(promise); + + setActive(); + doPull(); +} + +inline void ReadableStreamSource::startFinished() +{ + ASSERT(m_promise); + std::exchange(m_promise, std::nullopt).value().resolve(); + setInactive(); +} + +inline void ReadableStreamSource::pullFinished() +{ + ASSERT(m_promise); + std::exchange(m_promise, std::nullopt).value().resolve(); + setInactive(); +} + +inline void ReadableStreamSource::cancel(JSC::JSValue) +{ + clean(); + doCancel(); +} + +inline void ReadableStreamSource::clean() +{ + if (m_promise) { + m_promise = std::nullopt; + setInactive(); + } +} + +} // namespace WebCore + +#endif // ENABLE(READABLE_STREAM_API) diff --git a/Source/WebCore/Modules/streams/ReadableStreamSource.idl b/Source/WebCore/Modules/streams/ReadableStreamSource.idl new file mode 100644 index 000000000..ad3afcc99 --- /dev/null +++ b/Source/WebCore/Modules/streams/ReadableStreamSource.idl @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 Canon Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions + * are required to be met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Canon Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +[ + NoInterfaceObject, + Conditional=READABLE_STREAM_API, + SkipVTableValidation +] interface ReadableStreamSource { + [Custom] Promise<void> start(ReadableStreamDefaultController controller); + [Custom] Promise<void> pull(ReadableStreamDefaultController controller); + void cancel(any reason); + + // Place holder to keep the controller linked to the source. + [CachedAttribute, CustomGetter] readonly attribute any controller; +}; diff --git a/Source/WebCore/Modules/streams/StreamInternals.js b/Source/WebCore/Modules/streams/StreamInternals.js new file mode 100644 index 000000000..94a96e396 --- /dev/null +++ b/Source/WebCore/Modules/streams/StreamInternals.js @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015 Canon Inc. + * Copyright (C) 2015 Igalia. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// @conditional=ENABLE(READABLE_STREAM_API) || ENABLE(WRITABLE_STREAM_API) +// @internal + +function shieldingPromiseResolve(result) +{ + const promise = @Promise.@resolve(result); + if (promise.@then === @undefined) + promise.@then = @Promise.prototype.@then; + return promise; +} + +function promiseInvokeOrNoopNoCatch(object, key, args) +{ + "use strict"; + + const method = object[key]; + if (method === @undefined) + return @Promise.@resolve(); + return @shieldingPromiseResolve(method.@apply(object, args)); +} + +function promiseInvokeOrNoop(object, key, args) +{ + "use strict"; + + try { + return @promiseInvokeOrNoopNoCatch(object, key, args); + } + catch(error) { + return @Promise.@reject(error); + } + +} + +function promiseInvokeOrFallbackOrNoop(object, key1, args1, key2, args2) +{ + "use strict"; + + try { + const method = object[key1]; + if (method === @undefined) + return @promiseInvokeOrNoopNoCatch(object, key2, args2); + return @shieldingPromiseResolve(method.@apply(object, args1)); + } + catch(error) { + return @Promise.@reject(error); + } +} + +function validateAndNormalizeQueuingStrategy(size, highWaterMark) +{ + "use strict"; + + if (size !== @undefined && typeof size !== "function") + @throwTypeError("size parameter must be a function"); + + const normalizedStrategy = { }; + + normalizedStrategy.size = size; + normalizedStrategy.highWaterMark = @Number(highWaterMark); + + if (@isNaN(normalizedStrategy.highWaterMark) || normalizedStrategy.highWaterMark < 0) + @throwRangeError("highWaterMark value is negative or not a number"); + + return normalizedStrategy; +} + +function newQueue() +{ + "use strict"; + + return { content: [], size: 0 }; +} + +function dequeueValue(queue) +{ + "use strict"; + + const record = queue.content.@shift(); + queue.size -= record.size; + return record.value; +} + +function enqueueValueWithSize(queue, value, size) +{ + "use strict"; + + size = @Number(size); + if (!@isFinite(size) || size < 0) + @throwRangeError("size has an incorrect value"); + queue.content.@push({ value: value, size: size }); + queue.size += size; +} + +function peekQueueValue(queue) +{ + "use strict"; + + @assert(queue.content.length > 0); + + return queue.content[0].value; +} diff --git a/Source/WebCore/Modules/streams/WritableStream.idl b/Source/WebCore/Modules/streams/WritableStream.idl new file mode 100644 index 000000000..62e92234c --- /dev/null +++ b/Source/WebCore/Modules/streams/WritableStream.idl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 Canon Inc. + * Copyright (C) 2015 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions + * are required to be met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Canon Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +[ + Conditional=WRITABLE_STREAM_API, + Constructor, + JSBuiltin +] interface WritableStream { + Promise<any> abort(optional any reason); + Promise<any> close(); + Promise<any> write(any chunk); + + readonly attribute Promise<boolean> closed; + readonly attribute Promise<boolean> ready; + readonly attribute DOMString state; +}; diff --git a/Source/WebCore/Modules/streams/WritableStream.js b/Source/WebCore/Modules/streams/WritableStream.js new file mode 100644 index 000000000..ac6ca2649 --- /dev/null +++ b/Source/WebCore/Modules/streams/WritableStream.js @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2015 Canon Inc. + * Copyright (C) 2015 Igalia + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// @conditional=ENABLE(WRITABLE_STREAM_API) + +function initializeWritableStream(underlyingSink, strategy) +{ + "use strict"; + + if (underlyingSink === @undefined) + underlyingSink = { }; + if (strategy === @undefined) + strategy = { highWaterMark: 0, size: function() { return 1; } }; + + if (!@isObject(underlyingSink)) + @throwTypeError("WritableStream constructor takes an object as first argument"); + + if (!@isObject(strategy)) + @throwTypeError("WritableStream constructor takes an object as second argument, if any"); + + this.@underlyingSink = underlyingSink; + this.@closedPromiseCapability = @newPromiseCapability(@Promise); + this.@readyPromiseCapability = { @promise: @Promise.@resolve() }; + this.@queue = @newQueue(); + this.@state = @streamWritable; + this.@started = false; + this.@writing = false; + + this.@strategy = @validateAndNormalizeQueuingStrategy(strategy.size, strategy.highWaterMark); + + @syncWritableStreamStateWithQueue(this); + + const errorFunction = (e) => { + @errorWritableStream(this, e); + }; + this.@startedPromise = @promiseInvokeOrNoopNoCatch(underlyingSink, "start", [errorFunction]); + this.@startedPromise.@then(() => { + this.@started = true; + this.@startedPromise = @undefined; + }, errorFunction); + + return this; +} + +function abort(reason) +{ + "use strict"; + + if (!@isWritableStream(this)) + return @Promise.@reject(new @TypeError("The WritableStream.abort method can only be used on instances of WritableStream")); + + if (this.@state === @streamClosed) + return @Promise.@resolve(); + + if (this.@state === @streamErrored) + return @Promise.@reject(this.@storedError); + + @errorWritableStream(this, reason); + + return @promiseInvokeOrFallbackOrNoop(this.@underlyingSink, "abort", [reason], "close", []).@then(function() { }); +} + +function close() +{ + "use strict"; + + if (!@isWritableStream(this)) + return @Promise.@reject(new @TypeError("The WritableStream.close method can only be used on instances of WritableStream")); + + if (this.@state === @streamClosed || this.@state === @streamClosing) + return @Promise.@reject(new @TypeError("Cannot close a WritableString that is closed or closing")); + + if (this.@state === @streamErrored) + return @Promise.@reject(this.@storedError); + + if (this.@state === @streamWaiting) + this.@readyPromiseCapability.@resolve.@call(); + + this.@state = @streamClosing; + @enqueueValueWithSize(this.@queue, "close", 0); + @callOrScheduleWritableStreamAdvanceQueue(this); + + return this.@closedPromiseCapability.@promise; +} + +function write(chunk) +{ + "use strict"; + + if (!@isWritableStream(this)) + return @Promise.@reject(new @TypeError("The WritableStream.write method can only be used on instances of WritableStream")); + + if (this.@state === @streamClosed || this.@state === @streamClosing) + return @Promise.@reject(new @TypeError("Cannot write on a WritableString that is closed or closing")); + + if (this.@state === @streamErrored) + return @Promise.@reject(this.@storedError); + + @assert(this.@state === @streamWritable || this.@state === @streamWaiting); + + let chunkSize = 1; + if (this.@strategy.size !== @undefined) { + try { + chunkSize = this.@strategy.size.@call(@undefined, chunk); + } catch(e) { + @errorWritableStream(this, e); + return @Promise.@reject(e); + } + } + + const promiseCapability = @newPromiseCapability(@Promise); + try { + @enqueueValueWithSize(this.@queue, { promiseCapability: promiseCapability, chunk: chunk }, chunkSize); + } catch (e) { + @errorWritableStream(this, e); + return @Promise.@reject(e); + } + + @syncWritableStreamStateWithQueue(this); + @callOrScheduleWritableStreamAdvanceQueue(this); + + return promiseCapability.@promise; +} + +function closed() +{ + "use strict"; + + if (!@isWritableStream(this)) + return @Promise.@reject(new @TypeError("The WritableStream.closed getter can only be used on instances of WritableStream")); + + return this.@closedPromiseCapability.@promise; +} + +function ready() +{ + "use strict"; + + if (!@isWritableStream(this)) + return @Promise.@reject(new @TypeError("The WritableStream.ready getter can only be used on instances of WritableStream")); + + return this.@readyPromiseCapability.@promise; +} + +function state() +{ + "use strict"; + + if (!@isWritableStream(this)) + @throwTypeError("The WritableStream.state getter can only be used on instances of WritableStream"); + + switch(this.@state) { + case @streamClosed: + return "closed"; + case @streamClosing: + return "closing"; + case @streamErrored: + return "errored"; + case @streamWaiting: + return "waiting"; + case @streamWritable: + return "writable"; + } + + @assert(false); +} diff --git a/Source/WebCore/Modules/streams/WritableStreamInternals.js b/Source/WebCore/Modules/streams/WritableStreamInternals.js new file mode 100644 index 000000000..cffea5a3d --- /dev/null +++ b/Source/WebCore/Modules/streams/WritableStreamInternals.js @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015 Canon Inc. + * Copyright (C) 2015 Igalia + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// @conditional=ENABLE(WRITABLE_STREAM_API) +// @internal + +function isWritableStream(stream) +{ + "use strict"; + + return @isObject(stream) && !!stream.@underlyingSink; +} + +function syncWritableStreamStateWithQueue(stream) +{ + "use strict"; + + if (stream.@state === @streamClosing) + return; + + @assert(stream.@state === @streamWritable || stream.@state === @streamWaiting); + + const shouldApplyBackpressure = stream.@queue.size > stream.@strategy.highWaterMark; + if (shouldApplyBackpressure && stream.@state === @streamWritable) { + stream.@state = @streamWaiting; + stream.@readyPromiseCapability = @newPromiseCapability(@Promise); + } + if (!shouldApplyBackpressure && stream.@state === @streamWaiting) { + stream.@state = @streamWritable; + stream.@readyPromiseCapability.@resolve.@call(); + } +} + +function errorWritableStream(stream, e) +{ + "use strict"; + + if (stream.@state === @streamClosed || stream.@state === @streamErrored) + return; + while (stream.@queue.content.length > 0) { + const writeRecord = @dequeueValue(stream.@queue); + if (writeRecord !== "close") + writeRecord.promiseCapability.@reject.@call(@undefined, e); + } + stream.@storedError = e; + if (stream.@state === @streamWaiting) + stream.@readyPromiseCapability.@resolve.@call(); + stream.@closedPromiseCapability.@reject.@call(@undefined, e); + stream.@state = @streamErrored; +} + +function callOrScheduleWritableStreamAdvanceQueue(stream) +{ + "use strict"; + + if (!stream.@started) + stream.@startedPromise.@then(function() { @writableStreamAdvanceQueue(stream); }); + else + @writableStreamAdvanceQueue(stream); +} + +function writableStreamAdvanceQueue(stream) +{ + "use strict"; + + if (stream.@queue.content.length === 0 || stream.@writing) + return; + + const writeRecord = @peekQueueValue(stream.@queue); + if (writeRecord === "close") { + @assert(stream.@state === @streamClosing); + @dequeueValue(stream.@queue); + @assert(stream.@queue.content.length === 0); + @closeWritableStream(stream); + return; + } + + stream.@writing = true; + @promiseInvokeOrNoop(stream.@underlyingSink, "write", [writeRecord.chunk]).@then( + function() { + if (stream.@state === @streamErrored) + return; + stream.@writing = false; + writeRecord.promiseCapability.@resolve.@call(); + @dequeueValue(stream.@queue); + @syncWritableStreamStateWithQueue(stream); + @writableStreamAdvanceQueue(stream); + }, + function(r) { + @errorWritableStream(stream, r); + } + ); +} + +function closeWritableStream(stream) +{ + "use strict"; + + @assert(stream.@state === @streamClosing); + @promiseInvokeOrNoop(stream.@underlyingSink, "close").@then( + function() { + if (stream.@state === @streamErrored) + return; + @assert(stream.@state === @streamClosing); + stream.@closedPromiseCapability.@resolve.@call(); + stream.@state = @streamClosed; + }, + function(r) { + @errorWritableStream(stream, r); + } + ); +} |