1/*
2 * Copyright (C) 2015 Canon Inc.
3 * Copyright (C) 2015 Igalia S.L.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted, provided that the following conditions
7 * are required to be met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Canon Inc. nor the names of
15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR
22 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include "config.h"
31
32#if ENABLE(STREAMS_API)
33#include "JSReadableStreamSource.h"
34
35#include "ExceptionCode.h"
36#include "JSDOMPromise.h"
37#include "JSReadableStream.h"
38#include "ReadableStream.h"
39#include <runtime/Error.h>
40#include <runtime/JSArrayBuffer.h>
41#include <runtime/JSCJSValueInlines.h>
42#include <runtime/JSPromise.h>
43#include <runtime/JSPromiseDeferred.h>
44#include <runtime/JSString.h>
45#include <runtime/StructureInlines.h>
46#include <wtf/MainThread.h>
47
48using namespace JSC;
49
50
51namespace WebCore {
52
53// FIXME: Should we migrate to JSObjectGetPrivate/JSObjectSetPrivate in lieu of slots?
54static JSValue getSlotFromObject(ExecState* exec, JSValue objectValue, const char* identifier)
55{
56 JSObject* object = objectValue.toObject(exec);
57 PropertySlot propertySlot(objectValue);
58 PropertyName propertyName = Identifier(exec, identifier);
59 if (!object->getOwnPropertySlot(object, exec, propertyName, propertySlot))
60 return jsUndefined();
61 return propertySlot.getValue(exec, propertyName);
62}
63
64// We use setSlotToObject on JS functions so that we can retrieve the JSReadableStream object inside the corresponding JS function.
65static void setSlotToObject(ExecState* exec, JSValue objectValue, const char* identifier, JSValue value)
66{
67 JSObject* object = objectValue.toObject(exec);
68 PutPropertySlot propertySlot(objectValue);
69 object->put(object, exec, Identifier(exec, identifier), value, propertySlot);
70}
71
72static JSReadableStream* getJSReadableStream(ExecState* exec)
73{
74 JSReadableStream* jsReadableStream = jsDynamicCast<JSReadableStream*>(getSlotFromObject(exec, exec->callee(), "ReadableStream"));
75 ASSERT(jsReadableStream);
76 return jsReadableStream;
77}
78
79inline static JSValue getPropertyFromObject(ExecState* exec, JSObject* object, const char* identifier)
80{
81 return object->get(exec, Identifier(exec, identifier));
82}
83
84JSValue JSReadableStreamSource::callFunction(ExecState* exec, JSValue jsFunction, const ArgList& arguments, JSValue thisValue)
85{
86 JSLockHolder lock(exec);
87
88 CallData callData;
89 CallType callType = getCallData(jsFunction, callData);
90
91 JSValue exception;
92
93 JSValue value = call(exec, jsFunction, callType, callData, thisValue.isUndefined() ? m_readableStream : thisValue, arguments, &exception);
94
95 if (exception) {
96 m_error = exception;
97 return value;
98 }
99
100 if (callType == CallTypeNone) {
101 m_error = createError(exec, ASCIILiteral("Abrupt call."));
102 return value;
103 }
104
105 return value;
106}
107
108static void errorFromValueReadableStream(ExecState* exec, JSValue error)
109{
110 ASSERT(!error.isUndefined());
111
112 JSReadableStream* jsReadableStream = getJSReadableStream(exec);
113
114 String errorDescription = error.toString(exec)->value(exec);
115 static_cast<JSReadableStreamSource*>(jsReadableStream->impl().source())->setError(error);
116
117 jsReadableStream->impl().setError(errorDescription);
118 jsReadableStream->impl().createReadableStreamErrorFunction();
119}
120
121JSReadableStreamSource::JSReadableStreamSource(JSC::ExecState* exec)
122 : m_readableStream(nullptr)
123 , m_error(jsUndefined())
124{
125 m_startFunction.set(exec->vm(), jsUndefined());
126 m_pullFunction.set(exec->vm(), jsUndefined());
127 m_cancelFunction.set(exec->vm(), jsUndefined());
128 m_strategy.set(exec->vm(), jsUndefined());
129
130 if (!exec->argumentCount())
131 return;
132
133 if (!exec->argument(0).isObject()) {
134 m_error = createTypeError(exec, ASCIILiteral("ReadableStream constructor should get an object as argument."));
135 return;
136 }
137
138 JSValue argumentValue = exec->argument(0);
139 JSObject* argument = argumentValue.getObject();
140
141 JSValue startFunction = getPropertyFromObject(exec, argument, "start");
142 if (!startFunction.isUndefined() && !startFunction.isFunction()) {
143 m_error = createTypeError(exec, ASCIILiteral("ReadableStream constructor object start property should be functions."));
144 return;
145 }
146
147 JSValue pullFunction = getPropertyFromObject(exec, argument, "pull");
148 if (!pullFunction.isUndefined() && !pullFunction.isFunction()) {
149 m_error = createTypeError(exec, ASCIILiteral("ReadableStream constructor object pull property should be functions."));
150 return;
151 }
152
153 JSValue cancelFunction = getPropertyFromObject(exec, argument, "cancel");
154 if (!cancelFunction.isUndefined() && !cancelFunction.isFunction()) {
155 m_error = createTypeError(exec, ASCIILiteral("ReadableStream constructor object cancel property should be functions."));
156 return;
157 }
158
159 JSValue strategy = getPropertyFromObject(exec, argument, "strategy");
160 if (!strategy.isUndefined() && !strategy.isObject()) {
161 m_error = createTypeError(exec, ASCIILiteral("ReadableStream constructor strategy should be an object."));
162 return;
163 }
164
165 m_startFunction.set(exec->vm(), startFunction);
166 m_pullFunction.set(exec->vm(), pullFunction);
167 m_cancelFunction.set(exec->vm(), cancelFunction);
168 m_strategy.set(exec->vm(), strategy);
169}
170
171unsigned JSReadableStreamSource::chunkSize(ExecState* exec, JSValue chunk)
172{
173 // If we do not have a stored strategy, we return 1 as default.
174 if (!m_strategy.get().isObject())
175 return 1;
176
177 JSValue sizeFunction = m_strategy.get().getObject()->get(exec, Identifier(exec, "size"));
178
179 if (!sizeFunction.isFunction()) {
180 m_error = createError(exec, ASCIILiteral("No chunk size JS callback"));
181 return 0;
182 }
183
184 MarkedArgumentBuffer arguments;
185 arguments.append(chunk);
186 JSValue sizeValue = callFunction(exec, sizeFunction, arguments, m_strategy.get());
187
188 if (!m_error.isUndefined())
189 return 0;
190
191 int32_t value = sizeValue.toInt32(exec);
192 if (value < 0) {
193 m_error = createError(exec, ASCIILiteral("Incorrect value."));
194 return 0;
195 }
196 return value;
197}
198
199static EncodedJSValue JSC_HOST_CALL enqueueReadableStreamFunction(ExecState* exec)
200{
201 JSReadableStream* jsReadableStream = getJSReadableStream(exec);
202 JSReadableStreamSource* source = static_cast<JSReadableStreamSource*>(jsReadableStream->impl().source());
203
204 if (!source)
205 return throwVMError(exec, createTypeError(exec, ASCIILiteral("Unable to enqueue.")));
206
207 String errorDescription;
208 if (!jsReadableStream->impl().canEnqueue(errorDescription))
209 return throwVMError(exec, createTypeError(exec, errorDescription));
210
211 JSValue chunk = exec->argument(0);
212
213 unsigned chunkSize = source->chunkSize(exec, chunk);
214 JSValue sourceError = source->error();
215 if (!sourceError.isUndefined())
216 return throwVMError(exec, sourceError);
217
218 source->enqueue(chunk);
219 bool shouldApplyBackpressure = jsReadableStream->impl().enqueue(nullptr, 0, chunkSize);
220
221 return JSValue::encode(jsBoolean(!shouldApplyBackpressure));
222}
223
224static JSFunction* createReadableStreamEnqueueFunction(ExecState* exec)
225{
226 return JSFunction::create(exec->vm(), exec->callee()->globalObject(), 1, ASCIILiteral("CreateReadableStreamEnqueueFunction"), enqueueReadableStreamFunction);
227}
228
229static EncodedJSValue JSC_HOST_CALL closeReadableStreamFunction(ExecState* exec)
230{
231 JSReadableStream* jsReadableStream = getJSReadableStream(exec);
232 jsReadableStream->impl().createReadableStreamCloseFunction();
233 return JSValue::encode(jsUndefined());
234}
235
236static JSFunction* createReadableStreamCloseFunction(ExecState* exec)
237{
238 return JSFunction::create(exec->vm(), exec->callee()->globalObject(), 0, ASCIILiteral("CreateReadableStreamCloseFunction"), closeReadableStreamFunction);
239}
240
241static EncodedJSValue JSC_HOST_CALL errorReadableStreamFunction(ExecState* exec)
242{
243 JSValue error = exec->argumentCount() ? exec->argument(0) : createError(exec, ASCIILiteral("Error function called."));
244
245 errorFromValueReadableStream(exec, error);
246
247 return JSValue::encode(jsUndefined());
248}
249
250static JSFunction* createReadableStreamErrorFunction(ExecState* exec)
251{
252 return JSFunction::create(exec->vm(), exec->callee()->globalObject(), 1, ASCIILiteral("CreateReadableStreamErrorFunction"), errorReadableStreamFunction);
253}
254
255void JSReadableStreamSource::setStream(ExecState* exec, JSReadableStream* readableStream)
256{
257 m_readableStream = readableStream;
258
259 JSValue enqueueFunction = createReadableStreamEnqueueFunction(exec);
260 JSValue closeFunction = createReadableStreamCloseFunction(exec);
261 JSValue errorFunction = createReadableStreamErrorFunction(exec);
262
263 m_enqueueFunction.set(exec->vm(), enqueueFunction);
264 m_closeFunction.set(exec->vm(), closeFunction);
265 m_errorFunction.set(exec->vm(), errorFunction);
266
267 JSValue streamValue = m_readableStream;
268 setSlotToObject(exec, m_enqueueFunction.get(), "ReadableStream", streamValue);
269 setSlotToObject(exec, m_closeFunction.get(), "ReadableStream", streamValue);
270 setSlotToObject(exec, m_errorFunction.get(), "ReadableStream", streamValue);
271}
272
273PassRefPtr<ReadableStreamSource> JSReadableStreamSource::create(JSC::ExecState* exec)
274{
275 return (RefPtr<ReadableStreamSource>(adoptRef(new JSReadableStreamSource(exec)))).release();
276}
277
278static EncodedJSValue JSC_HOST_CALL cancelResultFulfilled(ExecState* exec)
279{
280 getJSReadableStream(exec)->impl().notifyCancel();
281 return JSValue::encode(jsUndefined());
282}
283
284static JSFunction* createCancelResultFulfilledFunction(ExecState* exec)
285{
286 return JSFunction::create(exec->vm(), exec->callee()->globalObject(), 1, ASCIILiteral("CancelResultFulfilledFunction"), cancelResultFulfilled);
287}
288
289static EncodedJSValue JSC_HOST_CALL cancelResultRejected(ExecState* exec)
290{
291 JSReadableStream* jsReadableStream = getJSReadableStream(exec);
292 jsReadableStream->impl().setError(ASCIILiteral("Cancel promise rejected."));
293 jsReadableStream->impl().notifyCancel();
294 return JSValue::encode(jsUndefined());
295}
296
297static JSFunction* createCancelResultRejectedFunction(ExecState* exec)
298{
299 return JSFunction::create(exec->vm(), exec->callee()->globalObject(), 1, ASCIILiteral("CancelResultRejectedFunction"), cancelResultRejected);
300}
301
302void JSReadableStreamSource::pull()
303{
304 // If we do not have a pull function stored, we do nothing as default.
305 if (m_pullFunction.get().isUndefined())
306 return;
307
308 MarkedArgumentBuffer arguments;
309 arguments.append(m_enqueueFunction.get());
310 arguments.append(m_closeFunction.get());
311 arguments.append(m_errorFunction.get());
312
313 ExecState* exec = m_readableStream->globalObject()->globalExec();
314 callFunction(exec, m_pullFunction.get(), arguments);
315}
316
317bool JSReadableStreamSource::cancel(const String& errorReason)
318{
319 // If we do not have a cancel function stored, we do nothing as default.
320 if (m_cancelFunction.get().isUndefined())
321 return true;
322
323 ExecState* exec = m_readableStream->globalObject()->globalExec();
324
325 JSValue cancelResultFulfilledFunction = createCancelResultFulfilledFunction(exec);
326 setSlotToObject(exec, cancelResultFulfilledFunction, "ReadableStream", m_readableStream);
327 JSValue cancelResultRejectedFunction = createCancelResultRejectedFunction(exec);
328 setSlotToObject(exec, cancelResultRejectedFunction, "ReadableStream", m_readableStream);
329
330 MarkedArgumentBuffer arguments;
331 arguments.append(jsString(exec, errorReason));
332
333 JSValue result = callFunction(exec, m_cancelFunction.get(), arguments);
334
335 if (!m_error.isUndefined()) {
336 // FIXME: We should reject the cancel promise with m_error.
337 m_readableStream->impl().setError(ASCIILiteral("Cancel callback exception."));
338 return true;
339 }
340
341 if (result.isUndefined())
342 return true;
343
344 if (!resolvePromise(exec, result, cancelResultFulfilledFunction, cancelResultRejectedFunction)) {
345 m_readableStream->impl().setError(ASCIILiteral("Cancel promise resolution failed."));
346 return true;
347 }
348
349 return false;
350}
351
352bool JSReadableStreamSource::shouldApplyBackpressure(unsigned queueSize)
353{
354 // Apply default strategy.
355 if (!m_strategy.get().isObject())
356 return queueSize > 1;
357
358 ExecState* exec = m_readableStream->globalObject()->globalExec();
359 JSValue shouldApplyBackpressureFunction = getPropertyFromObject(exec, m_strategy.get().getObject(), "shouldApplyBackpressure");
360
361 if (!shouldApplyBackpressureFunction.isFunction()) {
362 m_error = createError(exec, ASCIILiteral("No back pressure JS callback."));
363 return true;
364 }
365
366 MarkedArgumentBuffer arguments;
367 arguments.append(jsNumber(queueSize));
368 JSValue shouldApplyBackpressure = callFunction(exec, shouldApplyBackpressureFunction, arguments, m_strategy.get());
369
370 if (!m_error.isUndefined())
371 return false;
372
373 return shouldApplyBackpressure.toBoolean(exec);
374}
375
376static EncodedJSValue JSC_HOST_CALL startResultFulfilled(ExecState* exec)
377{
378 JSReadableStream* jsReadableStream = getJSReadableStream(exec);
379 jsReadableStream->impl().start();
380 return JSValue::encode(jsUndefined());
381}
382
383static JSFunction* createStartResultFulfilledFunction(ExecState* exec)
384{
385 return JSFunction::create(exec->vm(), exec->callee()->globalObject(), 1, ASCIILiteral("CreateStartResultFulfilledFunction"), startResultFulfilled);
386}
387
388static EncodedJSValue JSC_HOST_CALL startResultRejected(ExecState* exec)
389{
390 JSReadableStream* jsReadableStream = getJSReadableStream(exec);
391 jsReadableStream->impl().setError(ASCIILiteral("Start promise rejected."));
392 jsReadableStream->impl().createReadableStreamErrorFunction();
393 return JSValue::encode(jsUndefined());
394}
395
396static JSFunction* createStartResultRejectedFunction(ExecState* exec)
397{
398 return JSFunction::create(exec->vm(), exec->callee()->globalObject(), 1, ASCIILiteral("CreateStartResultRejectedFunction"), startResultRejected);
399}
400
401void JSReadableStreamSource::start()
402{
403 // No-op function?
404 if (m_startFunction.get().isUndefined()) {
405 // Resolve the promise asynchronously.
406 callOnMainThread([this] {
407 m_readableStream->impl().start();
408 });
409 return;
410 }
411
412 ExecState* exec = m_readableStream->globalObject()->globalExec();
413
414 MarkedArgumentBuffer arguments;
415 arguments.append(m_enqueueFunction.get());
416 arguments.append(m_closeFunction.get());
417 arguments.append(m_errorFunction.get());
418
419 JSValue startResultFulfilledFunction = createStartResultFulfilledFunction(exec);
420 setSlotToObject(exec, startResultFulfilledFunction, "ReadableStream", m_readableStream);
421 JSValue startResultRejectedFunction = createStartResultRejectedFunction(exec);
422 setSlotToObject(exec, startResultRejectedFunction, "ReadableStream", m_readableStream);
423
424 JSValue result = callFunction(exec, m_startFunction.get(), arguments);
425
426 if (!m_error.isUndefined())
427 return;
428
429 if (result.isUndefined()) {
430 // Resolve the promise asynchronously.
431 callOnMainThread([this] {
432 m_readableStream->impl().start();
433 });
434 return;
435 }
436
437 if (!resolvePromise(exec, result, startResultFulfilledFunction, startResultRejectedFunction))
438 m_error = createError(exec, ASCIILiteral("Start promise resolution failed."));
439}
440
441
442void JSReadableStreamSource::enqueue(JSValue value)
443{
444 ExecState* exec = m_readableStream->globalObject()->globalExec();
445 m_jsChunkQueue.insert(0, JSC::Strong<JSC::Unknown>(exec->vm(), value));
446}
447
448JSValue JSReadableStreamSource::read()
449{
450 return m_jsChunkQueue.takeLast().get();
451}
452
453} // namespace WebCore
454
455#endif