23de7e0a14ef8843e8de2d94bb068b6bab32839c
[WebKit-https.git] / Source / WebCore / Modules / streams / ReadableStream.js
1 /*
2  * Copyright (C) 2015 Canon Inc.
3  * Copyright (C) 2015 Igalia.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 // @conditional=ENABLE(STREAMS_API)
28
29 function initializeReadableStream(underlyingSource, strategy)
30 {
31     "use strict";
32
33      if (underlyingSource === @undefined)
34          underlyingSource = { };
35      if (strategy === @undefined)
36          strategy = { };
37
38     if (!@isObject(underlyingSource))
39         @throwTypeError("ReadableStream constructor takes an object as first argument");
40
41     if (strategy !== @undefined && !@isObject(strategy))
42         @throwTypeError("ReadableStream constructor takes an object as second argument, if any");
43
44     this.@state = @streamReadable;
45     this.@reader = @undefined;
46     this.@storedError = @undefined;
47     this.@disturbed = false;
48     // Initialized with null value to enable distinction with undefined case.
49     this.@readableStreamController = null;
50
51     const type = underlyingSource.type;
52     const typeString = @String(type);
53
54     if (typeString === "bytes") {
55         if (!@readableByteStreamAPIEnabled())
56             @throwTypeError("ReadableByteStreamController is not implemented");
57
58         if (strategy.highWaterMark === @undefined)
59             strategy.highWaterMark = 0;
60
61         let readableByteStreamControllerConstructor = @ReadableByteStreamController;
62         this.@readableStreamController = new @ReadableByteStreamController(this, underlyingSource, strategy.highWaterMark);
63     } else if (type === @undefined) {
64         if (strategy.highWaterMark === @undefined)
65             strategy.highWaterMark = 1;
66         this.@readableStreamController = new @ReadableStreamDefaultController(this, underlyingSource, strategy.size, strategy.highWaterMark);
67     } else
68         @throwRangeError("Invalid type for underlying source");
69
70     return this;
71 }
72
73 function cancel(reason)
74 {
75     "use strict";
76
77     if (!@isReadableStream(this))
78         return @Promise.@reject(@makeThisTypeError("ReadableStream", "cancel"));
79
80     if (@isReadableStreamLocked(this))
81         return @Promise.@reject(new @TypeError("ReadableStream is locked"));
82
83     return @readableStreamCancel(this, reason);
84 }
85
86 function getReader(options)
87 {
88     "use strict";
89
90     if (!@isReadableStream(this))
91         throw @makeThisTypeError("ReadableStream", "getReader");
92
93     if (options === @undefined)
94          options = { };
95
96     if (options.mode === @undefined)
97         return new @ReadableStreamDefaultReader(this);
98
99     // String conversion is required by spec, hence double equals.
100     if (options.mode == 'byob')
101         return new @ReadableStreamBYOBReader(this);
102
103     @throwRangeError("Invalid mode is specified");
104 }
105
106 function pipeThrough(streams, options)
107 {
108     "use strict";
109
110     const writable = streams.writable;
111     const readable = streams.readable;
112     const promise = this.pipeTo(writable, options);
113     if (@isPromise(promise))
114         promise.@promiseIsHandled = true;
115     return readable;
116 }
117
118 function pipeTo(destination)
119 {
120     "use strict";
121
122     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=159869.
123     // Built-in generator should be able to parse function signature to compute the function length correctly.
124     const options = arguments[1];
125
126     // FIXME: rewrite pipeTo so as to require to have 'this' as a ReadableStream and destination be a WritableStream.
127     // See https://github.com/whatwg/streams/issues/407.
128     // We should shield the pipeTo implementation at the same time.
129
130     const preventClose = @isObject(options) && !!options.preventClose;
131     const preventAbort = @isObject(options) && !!options.preventAbort;
132     const preventCancel = @isObject(options) && !!options.preventCancel;
133
134     const source = this;
135
136     let reader;
137     let lastRead;
138     let lastWrite;
139     let closedPurposefully = false;
140     let promiseCapability;
141
142     function doPipe() {
143         lastRead = reader.read();
144         @Promise.prototype.@then.@call(@Promise.all([lastRead, destination.ready]), function([{ value, done }]) {
145             if (done)
146                 closeDestination();
147             else if (destination.state === "writable") {
148                 lastWrite = destination.write(value);
149                 doPipe();
150             }
151         }, function(e) {
152             throw e;
153         });
154     }
155
156     function cancelSource(reason) {
157         if (!preventCancel) {
158             reader.cancel(reason);
159             reader.releaseLock();
160             promiseCapability.@reject.@call(@undefined, reason);
161         } else {
162             @Promise.prototype.@then.@call(lastRead, function() {
163                 reader.releaseLock();
164                 promiseCapability.@reject.@call(@undefined, reason);
165             });
166         }
167     }
168
169     function closeDestination() {
170         reader.releaseLock();
171
172         const destinationState = destination.state;
173         if (!preventClose && (destinationState === "waiting" || destinationState === "writable")) {
174             closedPurposefully = true;
175             @Promise.prototype.@then.@call(destination.close(), promiseCapability.@resolve, promiseCapability.@reject);
176         } else if (lastWrite !== @undefined)
177             @Promise.prototype.@then.@call(lastWrite, promiseCapability.@resolve, promiseCapability.@reject);
178         else
179             promiseCapability.@resolve.@call();
180
181     }
182
183     function abortDestination(reason) {
184         reader.releaseLock();
185
186         if (!preventAbort)
187             destination.abort(reason);
188         promiseCapability.@reject.@call(@undefined, reason);
189     }
190
191     promiseCapability = @newPromiseCapability(@Promise);
192
193     reader = source.getReader();
194
195     @Promise.prototype.@then.@call(reader.closed, @undefined, abortDestination);
196     @Promise.prototype.@then.@call(destination.closed,
197         function() {
198             if (!closedPurposefully)
199                 cancelSource(new @TypeError('destination is closing or closed and cannot be piped to anymore'));
200         },
201         cancelSource
202     );
203
204     doPipe();
205
206     return promiseCapability.@promise;
207 }
208
209 function tee()
210 {
211     "use strict";
212
213     if (!@isReadableStream(this))
214         throw @makeThisTypeError("ReadableStream", "tee");
215
216     return @readableStreamTee(this, false);
217 }
218
219 @getter
220 function locked()
221 {
222     "use strict";
223
224     if (!@isReadableStream(this))
225         throw @makeGetterTypeError("ReadableStream", "locked");
226
227     return @isReadableStreamLocked(this);
228 }