[Streams API] Implement pulling of a source by a ReadableStream
[WebKit-https.git] / Source / WebCore / bindings / js / ReadableJSStream.cpp
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 #include "ReadableJSStream.h"
32
33 #if ENABLE(STREAMS_API)
34
35 #include "DOMWrapperWorld.h"
36 #include "JSDOMPromise.h"
37 #include "JSReadableStream.h"
38 #include "JSReadableStreamController.h"
39 #include "NotImplemented.h"
40 #include "ScriptExecutionContext.h"
41 #include <runtime/Error.h>
42 #include <runtime/Exception.h>
43 #include <runtime/JSCJSValueInlines.h>
44 #include <runtime/JSString.h>
45 #include <runtime/StructureInlines.h>
46
47 using namespace JSC;
48
49 namespace WebCore {
50
51 static inline JSValue getPropertyFromObject(ExecState& exec, JSObject* object, const char* identifier)
52 {
53     return object->get(&exec, Identifier::fromString(&exec, identifier));
54 }
55
56 static inline JSValue callFunction(ExecState& exec, JSValue jsFunction, JSValue thisValue, const ArgList& arguments)
57 {
58     CallData callData;
59     CallType callType = getCallData(jsFunction, callData);
60     return call(&exec, jsFunction, callType, callData, thisValue, arguments);
61 }
62
63 JSValue ReadableJSStream::invoke(ExecState& exec, const char* propertyName)
64 {
65     JSValue function = getPropertyFromObject(exec, m_source.get(), propertyName);
66     if (exec.hadException())
67         return jsUndefined();
68
69     if (!function.isFunction()) {
70         if (!function.isUndefined())
71             throwVMError(&exec, createTypeError(&exec, ASCIILiteral("ReadableStream trying to call a property that is not callable")));
72         return jsUndefined();
73     }
74
75     MarkedArgumentBuffer arguments;
76     arguments.append(jsController(exec, globalObject()));
77     return callFunction(exec, function, m_source.get(), arguments);
78 }
79
80 JSDOMGlobalObject* ReadableJSStream::globalObject()
81 {
82     return jsDynamicCast<JSDOMGlobalObject*>(m_source->globalObject());
83 }
84
85 static void startReadableStreamAsync(ReadableStream& readableStream)
86 {
87     RefPtr<ReadableStream> stream = &readableStream;
88     stream->scriptExecutionContext()->postTask([stream](ScriptExecutionContext&) {
89         stream->start();
90     });
91 }
92
93 void ReadableJSStream::doStart(ExecState& exec)
94 {
95     JSLockHolder lock(&exec);
96
97     invoke(exec, "start");
98
99     if (exec.hadException())
100         return;
101
102     // FIXME: Implement handling promise as result of calling start function.
103     startReadableStreamAsync(*this);
104 }
105
106 void ReadableJSStream::doPull()
107 {
108     ExecState& state = *globalObject()->globalExec();
109     JSLockHolder lock(&state);
110
111     invoke(state, "pull");
112
113     if (state.hadException()) {
114         storeException(state);
115         ASSERT(!state.hadException());
116         return;
117     }
118     // FIXME: Implement handling promise as result of calling pull function.
119 }
120
121 RefPtr<ReadableJSStream> ReadableJSStream::create(ExecState& exec, ScriptExecutionContext& scriptExecutionContext)
122 {
123     JSObject* jsSource;
124     JSValue value = exec.argument(0);
125     if (value.isObject())
126         jsSource = value.getObject();
127     else if (!value.isUndefined()) {
128         throwVMError(&exec, createTypeError(&exec, ASCIILiteral("ReadableStream constructor first argument, if any, should be an object")));
129         return nullptr;
130     } else
131         jsSource = JSFinalObject::create(exec.vm(), JSFinalObject::createStructure(exec.vm(), exec.callee()->globalObject(), jsNull(), 1));
132
133     RefPtr<ReadableJSStream> readableStream = adoptRef(*new ReadableJSStream(scriptExecutionContext, exec, jsSource));
134     readableStream->doStart(exec);
135
136     if (exec.hadException())
137         return nullptr;
138
139     return readableStream;
140 }
141
142 ReadableJSStream::ReadableJSStream(ScriptExecutionContext& scriptExecutionContext, ExecState& exec, JSObject* source)
143     : ReadableStream(scriptExecutionContext)
144 {
145     m_source.set(exec.vm(), source);
146 }
147
148 JSValue ReadableJSStream::jsController(ExecState& exec, JSDOMGlobalObject* globalObject)
149 {
150     if (!m_controller)
151         m_controller = std::make_unique<ReadableStreamController>(*this);
152     return toJS(&exec, globalObject, m_controller.get());
153 }
154
155 void ReadableJSStream::storeException(JSC::ExecState& state)
156 {
157     JSValue exception = state.exception()->value();
158     state.clearException();
159     storeError(state, exception);
160 }
161
162 void ReadableJSStream::storeError(JSC::ExecState& exec)
163 {
164     storeError(exec, exec.argumentCount() ? exec.argument(0) : createError(&exec, ASCIILiteral("Error function called.")));
165 }
166
167 void ReadableJSStream::storeError(JSC::ExecState& exec, JSValue error)
168 {
169     if (m_error)
170         return;
171     m_error.set(exec.vm(), error);
172
173     changeStateToErrored();
174 }
175
176 bool ReadableJSStream::hasValue() const
177 {
178     return m_chunkQueue.size();
179 }
180
181 JSValue ReadableJSStream::read()
182 {
183     ASSERT(hasValue());
184
185     return m_chunkQueue.takeFirst().get();
186 }
187
188 void ReadableJSStream::enqueue(ExecState& exec)
189 {
190     ASSERT(!isCloseRequested());
191
192     if (!isReadable())
193         return;
194
195     JSValue chunk = exec.argumentCount() ? exec.argument(0) : jsUndefined();
196     if (resolveReadCallback(chunk)) {
197         pull();
198         return;
199     }
200
201     m_chunkQueue.append(JSC::Strong<JSC::Unknown>(exec.vm(), chunk));
202     // FIXME: Compute chunk size.
203     pull();
204 }
205
206 } // namespace WebCore
207
208 #endif