[Streams API] Implement ReadableStreamController enqueue
authoryouenn.fablet@crf.canon.fr <youenn.fablet@crf.canon.fr@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 4 Jun 2015 10:19:43 +0000 (10:19 +0000)
committeryouenn.fablet@crf.canon.fr <youenn.fablet@crf.canon.fr@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 4 Jun 2015 10:19:43 +0000 (10:19 +0000)
https://bugs.webkit.org/show_bug.cgi?id=145210

Reviewed by Darin Adler.

Source/WebCore:

Added possibility to enqueue any JSValue within ReadableJSStream.
They are stored in a Vector of strongified JSValue.

Added support for streams that contain data but are asked to close.
This is done through m_closeRequested boolean and splitting actual closing of the stream from changeStateToClosed().

Chunk size and backpressure mechanism is not yet implemented.
Neither is pulling once enqueued data is processed.

Covered by rebased tests.

* Modules/streams/ReadableStream.cpp:
(WebCore::ReadableStream::changeStateToClosed): Split method with newly added close().
(WebCore::ReadableStream::close): Does the actual closing of stream once stream has no more values.
(WebCore::ReadableStream::read): Close the stream when stream is emptied and close is requested.
(WebCore::ReadableStream::resolveReadCallback): Added to enable ReadableJSStream to resolve read callbacks immediatly at enqueue time.
* Modules/streams/ReadableStream.h:
(WebCore::ReadableStream::isErrored): Getter added for the custom binding code.
(WebCore::ReadableStream::isCloseRequested): Ditto.
* bindings/js/JSReadableStreamControllerCustom.cpp:
(WebCore::JSReadableStreamController::enqueue): binding code for enqueue, taking care of raising exception if readable stream cannot enqueue.
* bindings/js/ReadableJSStream.cpp:
(WebCore::ReadableJSStream::hasValue):
(WebCore::ReadableJSStream::read):
(WebCore::ReadableJSStream::enqueue):
* bindings/js/ReadableJSStream.h:

LayoutTests:

* streams/reference-implementation/bad-underlying-sources-expected.txt:
* streams/reference-implementation/count-queuing-strategy-expected.txt:
* streams/reference-implementation/count-queuing-strategy.html:
* streams/reference-implementation/readable-stream-expected.txt:
* streams/reference-implementation/readable-stream-reader-expected.txt:
* streams/reference-implementation/readable-stream-reader.html:
* streams/reference-implementation/readable-stream-templated-expected.txt:
* streams/reference-implementation/readable-stream-templated.html:
* streams/reference-implementation/readable-stream.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@185197 268f45cc-cd09-0410-ab3c-d52691b4dbfc

16 files changed:
LayoutTests/ChangeLog
LayoutTests/streams/reference-implementation/bad-underlying-sources-expected.txt
LayoutTests/streams/reference-implementation/count-queuing-strategy-expected.txt
LayoutTests/streams/reference-implementation/count-queuing-strategy.html
LayoutTests/streams/reference-implementation/readable-stream-expected.txt
LayoutTests/streams/reference-implementation/readable-stream-reader-expected.txt
LayoutTests/streams/reference-implementation/readable-stream-reader.html
LayoutTests/streams/reference-implementation/readable-stream-templated-expected.txt
LayoutTests/streams/reference-implementation/readable-stream-templated.html
LayoutTests/streams/reference-implementation/readable-stream.html
Source/WebCore/ChangeLog
Source/WebCore/Modules/streams/ReadableStream.cpp
Source/WebCore/Modules/streams/ReadableStream.h
Source/WebCore/bindings/js/JSReadableStreamControllerCustom.cpp
Source/WebCore/bindings/js/ReadableJSStream.cpp
Source/WebCore/bindings/js/ReadableJSStream.h

index 82ad0d0..8e27bf9 100644 (file)
@@ -1,3 +1,20 @@
+2015-06-04  Xabier Rodriguez Calvar  <calvaris@igalia.com> and Youenn Fablet <youenn.fablet@crf.canon.fr>
+
+        [Streams API] Implement ReadableStreamController enqueue
+        https://bugs.webkit.org/show_bug.cgi?id=145210
+
+        Reviewed by Darin Adler.
+
+        * streams/reference-implementation/bad-underlying-sources-expected.txt:
+        * streams/reference-implementation/count-queuing-strategy-expected.txt:
+        * streams/reference-implementation/count-queuing-strategy.html:
+        * streams/reference-implementation/readable-stream-expected.txt:
+        * streams/reference-implementation/readable-stream-reader-expected.txt:
+        * streams/reference-implementation/readable-stream-reader.html:
+        * streams/reference-implementation/readable-stream-templated-expected.txt:
+        * streams/reference-implementation/readable-stream-templated.html:
+        * streams/reference-implementation/readable-stream.html:
+
 2015-06-03  Zalan Bujtas  <zalan@apple.com>
 
         Use borderBoxRect instead of contentBoxRect for backdrop filter.
index b75581b..f737d5b 100644 (file)
@@ -18,7 +18,7 @@ FAIL Underlying source: strategy.size returning NaN assert_unreached: enqueue di
 FAIL Underlying source: strategy.size returning -Infinity assert_unreached: enqueue didn't throw Reached unreachable code
 FAIL Underlying source: strategy.size returning +Infinity assert_unreached: enqueue didn't throw Reached unreachable code
 PASS Underlying source: calling close twice on an empty stream should throw the second time 
-FAIL Underlying source: calling close twice on a non-empty stream should throw the second time assert_object_equals: read() should read the enqueued chunk property "done" expected object "[object Object]" got object "[object Object]"
+PASS Underlying source: calling close twice on a non-empty stream should throw the second time 
 FAIL Underlying source: calling close on an empty canceled stream should not throw cancel is not implemented
 FAIL Underlying source: calling close on a non-empty canceled stream should not throw cancel is not implemented
 PASS Underlying source: calling close after error should throw 
index d7cbb75..b9bb735 100644 (file)
@@ -2,7 +2,7 @@
 PASS Can construct a CountQueuingStrategy with a valid high water mark 
 PASS Gives a RangeError when the number is negative 
 PASS Can construct a readable stream with a valid CountQueuingStrategy 
-TIMEOUT Correctly governs the return value of a ReadableStream's enqueue function (HWM = 0) Test timed out
-FAIL Correctly governs the return value of a ReadableStream's enqueue function (HWM = 1) assert_equals: After 0 reads, 1st enqueue should return true (queue now contains 1 chunk) expected true but got false
-FAIL Correctly governs the return value of a ReadableStream's enqueue function (HWM = 4) assert_equals: After 0 reads, 1st enqueue should return true (queue now contains 1 chunk) expected true but got false
+FAIL Correctly governs the return value of a ReadableStream's enqueue function (HWM = 0) assert_equals: After 0 reads, 1st enqueue should return false (queue now contains 1 chunk) expected (boolean) false but got (undefined) undefined
+FAIL Correctly governs the return value of a ReadableStream's enqueue function (HWM = 1) assert_equals: After 0 reads, 1st enqueue should return true (queue now contains 1 chunk) expected (boolean) true but got (undefined) undefined
+FAIL Correctly governs the return value of a ReadableStream's enqueue function (HWM = 4) assert_equals: After 0 reads, 1st enqueue should return true (queue now contains 1 chunk) expected (boolean) true but got (undefined) undefined
 
index 82c387d..3b9645b 100644 (file)
@@ -17,7 +17,7 @@ test(function() {
     new ReadableStream({ strategy: new CountQueuingStrategy({ "highWaterMark": 4 }) });
 }, 'Can construct a readable stream with a valid CountQueuingStrategy');
 
-var test1 = async_test('Correctly governs the return value of a ReadableStream\'s enqueue function (HWM = 0)', { timeout: 50 });
+var test1 = async_test('Correctly governs the return value of a ReadableStream\'s enqueue function (HWM = 0)');
 test1.step(function() {
     var enqueue;
     var rs = new ReadableStream({
index 89ae49d..8017e1a 100644 (file)
@@ -7,19 +7,19 @@ PASS ReadableStream constructor can get initial garbage as pull argument
 PASS ReadableStream constructor can get initial garbage as strategy argument 
 TIMEOUT ReadableStream start should be able to return a promise Test timed out
 TIMEOUT ReadableStream start should be able to return a promise and reject it Test timed out
-FAIL ReadableStream should be able to enqueue different objects. assert_object_equals: value read should be the one enqueued property "done" expected object "[object Object]" got object "[object Object]"
+PASS ReadableStream should be able to enqueue different objects. 
 PASS ReadableStream: if start throws an error, it should be re-thrown 
 TIMEOUT ReadableStream: if pull rejects, it should error the stream Test timed out
-TIMEOUT ReadableStream: should not call pull until the previous pull call's promise fulfills Test timed out
+FAIL ReadableStream: should not call pull until the previous pull call's promise fulfills assert_equals: pull should have been called once after start, but not yet have been called a second time expected 1 but got 0
 PASS ReadableStream: should not call pull after start if the stream is now closed 
 FAIL ReadableStream: should call pull after enqueueing from inside pull (with no read requests), if strategy allows assert_equals: pull() should have been called four times expected 4 but got 0
 TIMEOUT ReadableStream pull should be able to close a stream. Test timed out
-FAIL ReadableStream: enqueue should throw when the stream is readable but draining assert_equals: the first enqueue should return true expected true but got false
-FAIL ReadableStream: enqueue should throw when the stream is closed assert_throws: enqueue after close should throw a TypeError function "function () { c.enqueue('a'); }" did not throw
-FAIL ReadableStream: enqueue should throw the stored error when the stream is errored assert_throws: enqueue after error should throw that error function "function () { c.enqueue('a'); }" did not throw
-TIMEOUT ReadableStream: should call underlying source methods as methods Test timed out
-FAIL ReadableStream strategies: the default strategy should return false for all but the first enqueue call assert_equals: first enqueue should return true expected true but got false
-FAIL ReadableStream strategies: the default strategy should continue returning true from enqueue if the chunks are read immediately assert_equals: first enqueue should return true expected true but got false
+FAIL ReadableStream: enqueue should throw when the stream is readable but draining assert_equals: the first enqueue should return true expected (boolean) true but got (undefined) undefined
+PASS ReadableStream: enqueue should throw when the stream is closed 
+PASS ReadableStream: enqueue should throw the stored error when the stream is errored 
+FAIL ReadableStream: should call underlying source methods as methods releaseLock is not implemented
+FAIL ReadableStream strategies: the default strategy should return false for all but the first enqueue call assert_equals: first enqueue should return true expected (boolean) true but got (undefined) undefined
+FAIL ReadableStream strategies: the default strategy should continue returning true from enqueue if the chunks are read immediately assert_equals: first enqueue should return true expected (boolean) true but got (undefined) undefined
 TIMEOUT ReadableStream integration test: adapting a random push source Test timed out
 TIMEOUT ReadableStream integration test: adapting a sync pull source Test timed out
 TIMEOUT ReadableStream integration test: adapting an async pull source Test timed out
index 07c5d74..e000184 100644 (file)
@@ -9,7 +9,7 @@ PASS Constructing a ReadableStreamReader directly should fail if the stream is a
 PASS Getting a ReadableStreamReader via getReader should fail if the stream is already locked (via direct getReader) 
 PASS Constructing a ReadableStreamReader directly should be OK if the stream is closed 
 PASS Constructing a ReadableStreamReader directly should be OK if the stream is errored 
-TIMEOUT Reading from a reader for an empty stream will wait until a chunk is available Test timed out
+PASS Reading from a reader for an empty stream will wait until a chunk is available 
 FAIL cancel() on a reader releases the reader before calling through cancel is not implemented
 PASS closed should be fulfilled after stream is closed (.closed access before acquiring) 
 FAIL closed should be fulfilled after reader releases its lock (multiple stream locks) releaseLock is not implemented
index 813e01e..358343a 100644 (file)
@@ -107,7 +107,7 @@ test(function() {
     new ReadableStreamReader(rs); // Constructing directly should not throw.
 }, 'Constructing a ReadableStreamReader directly should be OK if the stream is errored');
 
-var test1 = async_test('Reading from a reader for an empty stream will wait until a chunk is available', { timeout: 50 });
+var test1 = async_test('Reading from a reader for an empty stream will wait until a chunk is available');
 test1.step(function() {
     var controller;
     var rs = new ReadableStream({
index 3e8c2c1..d61bc90 100644 (file)
@@ -30,13 +30,13 @@ FAIL cancel() should return a distinct rejected promise each time cancel is not
 FAIL reader cancel() should return a distinct rejected promise each time cancel is not implemented
 PASS should be able to acquire multiple readers, since they are all auto-released 
 PASS Running templatedRSTwoChunksOpenReader with ReadableStream (two chunks enqueued, still open) reader 
-TIMEOUT calling read() twice without waiting will eventually give both chunks Test timed out
-TIMEOUT calling read() twice with waiting will eventually give both chunks Test timed out
+PASS calling read() twice without waiting will eventually give both chunks 
+PASS calling read() twice with waiting will eventually give both chunks 
 PASS read() should return distinct promises each time 
 FAIL cancel() after a read() should still give that single read result cancel is not implemented
 PASS Running templatedRSTwoChunksClosedReader with ReadableStream (two chunks enqueued, then closed) reader 
-FAIL third read(), without waiting, should give { value: undefined, done: true } assert_object_equals: first result should be correct property "done" expected object "[object Object]" got object "[object Object]"
-FAIL third read, with waiting, should give { value: undefined, done: true } assert_object_equals: first result should be correct property "done" expected object "[object Object]" got object "[object Object]"
+PASS third read(), without waiting, should give { value: undefined, done: true } 
+PASS third read, with waiting, should give { value: undefined, done: true } 
 PASS draining the stream via read() should cause the reader closed promise to fulfill 
 FAIL releasing the lock after the stream is closed should do nothing releaseLock is not implemented
 FAIL releasing the lock should cause read() to act as if the stream is closed releaseLock is not implemented
index ac2c4c2..76a3778 100644 (file)
@@ -369,7 +369,7 @@ function templatedRSTwoChunksOpenReader(label, factory, chunks) {
     test(function() {
     }, 'Running templatedRSTwoChunksOpenReader with ' + label);
 
-    var test1 = async_test('calling read() twice without waiting will eventually give both chunks', { timeout: 50 });
+    var test1 = async_test('calling read() twice without waiting will eventually give both chunks');
     test1.step(function() {
         var { reader } = factory();
         var promiseCount = 0;
index 16ce8c4..eab1f09 100644 (file)
@@ -368,7 +368,7 @@ test4.step(function() {
 //     }), standardTimeout);
 // });
 
-var test11 = async_test('ReadableStream: should not call pull until the previous pull call\'s promise fulfills', { timeout: 50 });
+var test11 = async_test('ReadableStream: should not call pull until the previous pull call\'s promise fulfills');
 test11.step(function() {
     var resolve;
     var returnedPromise;
@@ -565,7 +565,7 @@ test(function() {
     });
 }, 'ReadableStream: enqueue should throw the stored error when the stream is errored');
 
-var test16 = async_test('ReadableStream: should call underlying source methods as methods', { timeout: 50 });
+var test16 = async_test('ReadableStream: should call underlying source methods as methods');
 test16.step(function() {
     var startCalled = 0;
     var pullCalled = 0;
index 00621b2..e91c851 100644 (file)
@@ -1,5 +1,39 @@
 2015-06-04  Xabier Rodriguez Calvar  <calvaris@igalia.com> and Youenn Fablet <youenn.fablet@crf.canon.fr>
 
+        [Streams API] Implement ReadableStreamController enqueue
+        https://bugs.webkit.org/show_bug.cgi?id=145210
+
+        Reviewed by Darin Adler.
+
+        Added possibility to enqueue any JSValue within ReadableJSStream.
+        They are stored in a Vector of strongified JSValue.
+
+        Added support for streams that contain data but are asked to close.
+        This is done through m_closeRequested boolean and splitting actual closing of the stream from changeStateToClosed().
+
+        Chunk size and backpressure mechanism is not yet implemented.
+        Neither is pulling once enqueued data is processed.
+
+        Covered by rebased tests.
+
+        * Modules/streams/ReadableStream.cpp:
+        (WebCore::ReadableStream::changeStateToClosed): Split method with newly added close().
+        (WebCore::ReadableStream::close): Does the actual closing of stream once stream has no more values.
+        (WebCore::ReadableStream::read): Close the stream when stream is emptied and close is requested.
+        (WebCore::ReadableStream::resolveReadCallback): Added to enable ReadableJSStream to resolve read callbacks immediatly at enqueue time.
+        * Modules/streams/ReadableStream.h:
+        (WebCore::ReadableStream::isErrored): Getter added for the custom binding code.
+        (WebCore::ReadableStream::isCloseRequested): Ditto.
+        * bindings/js/JSReadableStreamControllerCustom.cpp:
+        (WebCore::JSReadableStreamController::enqueue): binding code for enqueue, taking care of raising exception if readable stream cannot enqueue.
+        * bindings/js/ReadableJSStream.cpp:
+        (WebCore::ReadableJSStream::hasValue):
+        (WebCore::ReadableJSStream::read):
+        (WebCore::ReadableJSStream::enqueue):
+        * bindings/js/ReadableJSStream.h:
+
+2015-06-04  Xabier Rodriguez Calvar  <calvaris@igalia.com> and Youenn Fablet <youenn.fablet@crf.canon.fr>
+
         [Streams API] ReadableJSStream does not need a ReadableStreamSource
         https://bugs.webkit.org/show_bug.cgi?id=145601
 
index 80c0562..4f1c896 100644 (file)
@@ -67,8 +67,18 @@ void ReadableStream::clearCallbacks()
 
 void ReadableStream::changeStateToClosed()
 {
-    if (m_state != State::Readable)
+    ASSERT(!m_closeRequested);
+    ASSERT(m_state != State::Errored);
+
+    m_closeRequested = true;
+
+    if (m_state != State::Readable || hasValue())
         return;
+    close();
+}
+
+void ReadableStream::close()
+{
     m_state = State::Closed;
 
     if (m_reader)
@@ -144,12 +154,24 @@ void ReadableStream::read(ReadSuccessCallback&& successCallback, ReadEndCallback
     }
     if (hasValue()) {
         successCallback(read());
+        if (m_closeRequested && !hasValue())
+            close();
         return;
     }
     m_readRequests.append({ WTF::move(successCallback), WTF::move(endCallback), WTF::move(failureCallback) });
     // FIXME: We should try to pull.
 }
 
+bool ReadableStream::resolveReadCallback(JSC::JSValue value)
+{
+    if (m_readRequests.isEmpty())
+        return false;
+
+    m_readRequests.first().successCallback(value);
+    m_readRequests.remove(0);
+    return true;
+}
+
 void ReadableStream::start()
 {
     notImplemented();
index d1d3344..f62d7c2 100644 (file)
@@ -66,7 +66,9 @@ public:
     const ReadableStreamReader* reader() const { return m_reader.get(); }
     bool isLocked() const { return !!m_reader; }
 
+    bool isErrored() const { return m_state == State::Errored; }
     bool isReadable() const { return m_state == State::Readable; }
+    bool isCloseRequested() const { return m_closeRequested; }
 
     virtual JSC::JSValue error() = 0;
 
@@ -86,12 +88,15 @@ public:
 protected:
     explicit ReadableStream(ScriptExecutionContext&);
 
+    bool resolveReadCallback(JSC::JSValue);
+
 private:
     // ActiveDOMObject API.
     const char* activeDOMObjectName() const override;
     bool canSuspendForPageCache() const override;
 
     void clearCallbacks();
+    void close();
 
     virtual bool hasValue() const = 0;
     virtual JSC::JSValue read() = 0;
@@ -109,6 +114,7 @@ private:
     };
     Vector<ReadCallbacks> m_readRequests;
 
+    bool m_closeRequested { false };
     State m_state { State::Readable };
 };
 
index d91d33b..f1b587e 100644 (file)
@@ -33,7 +33,6 @@
 #if ENABLE(STREAMS_API)
 
 #include "JSDOMBinding.h"
-#include "NotImplemented.h"
 #include "ReadableJSStream.h"
 #include <runtime/Error.h>
 
@@ -51,10 +50,15 @@ JSValue JSReadableStreamController::close(ExecState* exec)
     return jsUndefined();
 }
 
-JSValue JSReadableStreamController::enqueue(ExecState*)
+JSValue JSReadableStreamController::enqueue(ExecState* exec)
 {
-    notImplemented();
-    return jsBoolean(false);
+    ReadableJSStream& stream = impl().stream();
+    if (stream.isErrored())
+        return exec->vm().throwException(exec, stream.error());
+    if (stream.isCloseRequested())
+        return exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Calling enqueue on a stream which is closing")));
+    stream.enqueue(*exec);
+    return jsUndefined();
 }
 
 JSValue JSReadableStreamController::error(ExecState* exec)
index c9593a9..1a55a5b 100644 (file)
@@ -135,14 +135,30 @@ void ReadableJSStream::storeError(JSC::ExecState& exec)
 
 bool ReadableJSStream::hasValue() const
 {
-    notImplemented();
-    return false;
+    return m_chunkQueue.size();
 }
 
 JSValue ReadableJSStream::read()
 {
-    notImplemented();
-    return jsUndefined();
+    ASSERT(hasValue());
+
+    return m_chunkQueue.takeFirst().get();
+}
+
+void ReadableJSStream::enqueue(ExecState& exec)
+{
+    ASSERT(!isCloseRequested());
+
+    if (!isReadable())
+        return;
+
+    JSValue chunk = exec.argumentCount() ? exec.argument(0) : jsUndefined();
+    if (resolveReadCallback(chunk))
+        return;
+
+    m_chunkQueue.append(JSC::Strong<JSC::Unknown>(exec.vm(), chunk));
+    // FIXME: Compute chunk size.
+    // FIXME: Add pulling of data here and also when data is passed to resolve callback.
 }
 
 } // namespace WebCore
index 6c572fe..67dc0c0 100644 (file)
@@ -37,6 +37,7 @@
 #include <heap/StrongInlines.h>
 #include <runtime/JSCJSValue.h>
 #include <runtime/PrivateName.h>
+#include <wtf/Deque.h>
 #include <wtf/Ref.h>
 
 namespace WebCore {
@@ -53,6 +54,8 @@ public:
     void storeError(JSC::ExecState&);
     JSC::JSValue error() { return m_error.get(); }
 
+    void enqueue(JSC::ExecState&);
+
 private:
     ReadableJSStream(ScriptExecutionContext&, JSC::ExecState&, JSC::JSObject*);
 
@@ -66,6 +69,7 @@ private:
     std::unique_ptr<ReadableStreamController> m_controller;
     JSC::Strong<JSC::Unknown> m_error;
     JSC::Strong<JSC::JSObject> m_source;
+    Deque<JSC::Strong<JSC::Unknown>> m_chunkQueue;
 };
 
 } // namespace WebCore