Add 'stream' to XMLHttpRequest response type.

Make XMLHttpRequest return a ReadableStream when the response type is set to
'stream'. This CL also modifies ReadableStream interface and implementation to
make it enable for XMLHttpRequest to use ReadableStream functionalities.

BUG=401396

Review URL: https://codereview.chromium.org/455303002

git-svn-id: svn://svn.chromium.org/blink/trunk@180432 bbb929c8-8fbe-4397-9dbb-9b2b20218538
diff --git a/LayoutTests/http/tests/xmlhttprequest/response-stream-abort.html b/LayoutTests/http/tests/xmlhttprequest/response-stream-abort.html
new file mode 100644
index 0000000..96b8299
--- /dev/null
+++ b/LayoutTests/http/tests/xmlhttprequest/response-stream-abort.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script type="text/javascript">
+var testInLoadingState = async_test('Test aborting XMLHttpRequest with responseType set to "stream" in LOADING state.');
+
+testInLoadingState.step(function()
+{
+    var xhr = new XMLHttpRequest;
+
+    xhr.responseType = 'stream';
+
+    var seenStates = [];
+
+    xhr.onreadystatechange = testInLoadingState.step_func(function() {
+        // onreadystatechange can be invoked multiple times in LOADING state.
+        if (seenStates.length == 0 || xhr.readyState != seenStates[seenStates.length - 1])
+            seenStates.push(xhr.readyState);
+
+        switch (xhr.readyState) {
+        case xhr.UNSENT:
+            assert_unreached('Unexpected readyState: UNSENT');
+            return;
+
+        case xhr.OPENED:
+        case xhr.HEADERS_RECEIVED:
+            return;
+
+        case xhr.LOADING:
+            var stream = xhr.response;
+            assert_true(stream instanceof ReadableStream, 'xhr.response shoud be ReadableStream');
+            assert_equals(stream.state, 'readable', 'stream state before abort() call');
+            assert_array_equals(seenStates, [xhr.OPENED, xhr.HEADERS_RECEIVED, xhr.LOADING]);
+
+            xhr.abort();
+
+            assert_equals(stream.state, 'errored', 'stream state after abort() call');
+            assert_equals(xhr.readyState, xhr.UNSENT, 'xhr.readyState after abort() call');
+            assert_equals(xhr.response, null, 'xhr.response after abort() call');
+            assert_array_equals(seenStates, [xhr.OPENED, xhr.HEADERS_RECEIVED, xhr.LOADING, xhr.DONE]);
+            testInLoadingState.done();
+            return;
+
+        case xhr.DONE:
+            return;
+
+        default:
+            assert_unreached('Unexpected readyState: ' + xhr.readyState);
+            return;
+        }
+    });
+
+    xhr.open('GET', '../resources/test.ogv', true);
+    xhr.send();
+});
+
+var testInDoneState = async_test('Test aborting XMLHttpRequest with responseType set to "stream" in DONE state.');
+
+testInDoneState.step(function()
+{
+    var xhr = new XMLHttpRequest;
+
+    xhr.responseType = 'stream';
+
+    var seenStates = [];
+
+    xhr.onreadystatechange = testInDoneState.step_func(function() {
+        // onreadystatechange can be invoked multiple times in LOADING state.
+        if (seenStates.length == 0 || xhr.readyState != seenStates[seenStates.length - 1])
+            seenStates.push(xhr.readyState);
+
+        switch (xhr.readyState) {
+        case xhr.UNSENT:
+        case xhr.OPENED:
+        case xhr.HEADERS_RECEIVED:
+        case xhr.LOADING:
+            return;
+
+        case xhr.DONE:
+            var stream = xhr.response;
+            assert_true(stream instanceof ReadableStream, 'xhr.response shoud be ReadableStream');
+            assert_equals(stream.state, 'readable', 'stream state before abort() call');
+            assert_equals(xhr.status, 200, 'xhr.status');
+            assert_not_equals(xhr.response, null, 'xhr.response during DONE');
+
+            xhr.abort();
+
+            assert_equals(stream.state, 'errored', 'stream state after abort() call');
+            assert_equals(xhr.readyState, xhr.UNSENT, 'xhr.readyState after abort() call');
+            assert_equals(xhr.response, null, 'xhr.response after abort() call');
+
+            assert_array_equals(seenStates, [xhr.OPENED, xhr.HEADERS_RECEIVED, xhr.LOADING, xhr.DONE]);
+            testInDoneState.done();
+            return;
+
+        default:
+            assert_unreached('Unexpected readyState: ' + xhr.readyState);
+            return;
+        }
+    });
+
+    xhr.open('GET', '../resources/test.ogv', true);
+    xhr.send();
+});
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/xmlhttprequest/response-stream-cancel.html b/LayoutTests/http/tests/xmlhttprequest/response-stream-cancel.html
new file mode 100644
index 0000000..6aaa183
--- /dev/null
+++ b/LayoutTests/http/tests/xmlhttprequest/response-stream-cancel.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script type="text/javascript">
+var test = async_test('Test canceling XMLHttpRequest with responseType set to "stream".');
+
+test.step(function()
+{
+    var xhr = new XMLHttpRequest;
+
+    xhr.responseType = 'stream';
+
+    var seenStates = [];
+
+    xhr.onreadystatechange = test.step_func(function() {
+        // onreadystatechange can be invoked multiple times in LOADING state.
+        if (seenStates.length == 0 || xhr.readyState != seenStates[seenStates.length - 1])
+            seenStates.push(xhr.readyState);
+
+        switch (xhr.readyState) {
+        case xhr.UNSENT:
+            assert_unreached('Unexpected readyState: UNSENT');
+            return;
+
+        case xhr.OPENED:
+            assert_equals(xhr.response, null, 'xhr.response during OPENED');
+            return;
+
+        case xhr.HEADERS_RECEIVED:
+            assert_equals(xhr.response, null, 'xhr.response during HEADERS_RECEIVED');
+            return;
+
+        case xhr.LOADING:
+            var stream = xhr.response;
+            assert_true(stream instanceof ReadableStream,
+                'xhr.response should be ReadableStream during LOADING');
+            stream.cancel('canceled via ReadableStream.cancel');
+            assert_equals(stream.state, 'closed', 'stream.state after cancel');
+
+            // Check that we saw all states.
+            assert_array_equals(seenStates,
+                                [xhr.OPENED, xhr.HEADERS_RECEIVED, xhr.LOADING, xhr.DONE]);
+
+            assert_equals(xhr.readyState, xhr.UNSENT, 'xhr.readyState after cancel');
+            assert_equals(xhr.response, null, 'xhr.response after cancel');
+            stream.closed.then(test.step_func(function(value) {
+                assert_equals(value, undefined,
+                    'stream.closed should be resolved with undefined');
+                test.done();
+            }), test.step_func(function() {
+                assert_unreached('stream.closed should not be rejected');
+            }));
+
+        case xhr.DONE:
+            return;
+
+        default:
+            assert_unreached('Unexpected readyState: ' + xhr.readyState)
+            return;
+        }
+    });
+
+    xhr.open('GET', '../resources/load-and-stall.php?name=test.ogv&stallAt=32768&stallFor=10', true);
+    xhr.send();
+});
+</script>
diff --git a/LayoutTests/http/tests/xmlhttprequest/response-stream.html b/LayoutTests/http/tests/xmlhttprequest/response-stream.html
new file mode 100644
index 0000000..ca24a9e
--- /dev/null
+++ b/LayoutTests/http/tests/xmlhttprequest/response-stream.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script type="text/javascript">
+var test = async_test('Test response of XMLHttpRequest with responseType set to "stream" for various readyState.');
+
+test.step(function()
+{
+    var xhr = new XMLHttpRequest;
+
+    xhr.responseType = 'stream';
+    assert_equals(xhr.responseType, 'stream', 'xhr.responseType');
+
+    assert_equals(xhr.readyState, xhr.UNSENT, 'xhr.readyState');
+    assert_equals(xhr.response, null, 'xhr.response during UNSENT');
+
+    var seenStates = [];
+
+    function readStream(stream) {
+        var chunks = [];
+        function rec(resolve, reject) {
+            while (stream.state === 'readable') {
+                chunks.push(stream.read());
+            }
+            if (stream.state === 'closed') {
+                resolve(chunks);
+                return;
+            }
+            stream.wait().then(function() {
+                rec(resolve, reject);
+            }).catch(reject);
+        }
+        return new Promise(rec);
+    }
+    var streamPromise = undefined;
+
+    xhr.onreadystatechange = test.step_func(function() {
+        // onreadystatechange can be invoked multiple times in LOADING state.
+        if (seenStates.length == 0 || xhr.readyState != seenStates[seenStates.length - 1])
+            seenStates.push(xhr.readyState);
+
+        switch (xhr.readyState) {
+        case xhr.UNSENT:
+            assert_unreached('Unexpected readyState: UNSENT');
+            return;
+
+        case xhr.OPENED:
+            assert_equals(xhr.response, null, 'xhr.response during OPENED');
+            return;
+
+        case xhr.HEADERS_RECEIVED:
+            assert_equals(xhr.response, null, 'xhr.response during HEADERS_RECEIVED');
+            return;
+
+        case xhr.LOADING:
+            assert_not_equals(xhr.response, null, 'xhr.response during LOADING');
+            assert_true(xhr.response instanceof ReadableStream,
+                'xhr.response should be ReadableStream during LOADING');
+            if (streamPromise === undefined) {
+                streamPromise = readStream(xhr.response);
+            }
+            return;
+
+        case xhr.DONE:
+            assert_equals(xhr.status, 200, 'xhr.status');
+
+            // Check that we saw all states.
+            assert_array_equals(seenStates,
+                [xhr.OPENED, xhr.HEADERS_RECEIVED, xhr.LOADING, xhr.DONE]);
+
+            assert_not_equals(streamPromise, undefined, 'streamPromise');
+            streamPromise.then(test.step_func(function(chunks) {
+                assert_equals(xhr.response.state, 'closed', 'stream status');
+                var size = 0;
+                for (var i = 0; i < chunks.length; ++i) {
+                    size += chunks[i].byteLength;
+                }
+                assert_equals(size, 103746, 'response size');
+                test.done();
+            }), test.step_func(function(e) {
+                assert_unreached('failed to read the response stream: ' + e);
+            }));
+            return;
+
+        default:
+            assert_unreached('Unexpected readyState: ' + xhr.readyState)
+            return;
+        }
+    });
+
+    xhr.open('GET', '../resources/test.ogv', true);
+    xhr.send();
+});
+</script>
diff --git a/Source/bindings/core/v8/custom/V8XMLHttpRequestCustom.cpp b/Source/bindings/core/v8/custom/V8XMLHttpRequestCustom.cpp
index 2ea73f3..43de108 100644
--- a/Source/bindings/core/v8/custom/V8XMLHttpRequestCustom.cpp
+++ b/Source/bindings/core/v8/custom/V8XMLHttpRequestCustom.cpp
@@ -38,11 +38,13 @@
 #include "bindings/core/v8/V8Document.h"
 #include "bindings/core/v8/V8FormData.h"
 #include "bindings/core/v8/V8HTMLDocument.h"
+#include "bindings/core/v8/V8ReadableStream.h"
 #include "bindings/core/v8/V8Stream.h"
 #include "bindings/core/v8/custom/V8ArrayBufferCustom.h"
 #include "bindings/core/v8/custom/V8ArrayBufferViewCustom.h"
 #include "core/dom/Document.h"
 #include "core/inspector/InspectorInstrumentation.h"
+#include "core/streams/ReadableStream.h"
 #include "core/streams/Stream.h"
 #include "core/workers/WorkerGlobalScope.h"
 #include "core/xml/XMLHttpRequest.h"
@@ -132,7 +134,14 @@
 
     case XMLHttpRequest::ResponseTypeLegacyStream:
         {
-            Stream* stream = xmlHttpRequest->responseStream();
+            Stream* stream = xmlHttpRequest->responseLegacyStream();
+            v8SetReturnValueFast(info, stream, xmlHttpRequest);
+            return;
+        }
+
+    case XMLHttpRequest::ResponseTypeStream:
+        {
+            ReadableStream* stream = xmlHttpRequest->responseStream();
             v8SetReturnValueFast(info, stream, xmlHttpRequest);
             return;
         }
diff --git a/Source/core/streams/ReadableStream.cpp b/Source/core/streams/ReadableStream.cpp
index b67e023..d87c562 100644
--- a/Source/core/streams/ReadableStream.cpp
+++ b/Source/core/streams/ReadableStream.cpp
@@ -16,43 +16,39 @@
 
 namespace blink {
 
-class ReadableStream::OnStarted : public ScriptFunction {
-public:
-    OnStarted(v8::Isolate* isolate, ReadableStream* stream)
-        : ScriptFunction(isolate)
-        , m_stream(stream) { }
-    virtual ScriptValue call(ScriptValue value) OVERRIDE
-    {
-        m_stream->onStarted();
-        return value;
-    }
-
-private:
-    Persistent<ReadableStream> m_stream;
-};
-
-ReadableStream::ReadableStream(ScriptState* scriptState, UnderlyingSource* source, ExceptionState* exceptionState)
-    : ContextLifecycleObserver(scriptState->executionContext())
-    , m_source(source)
+ReadableStream::ReadableStream(ExecutionContext* executionContext, UnderlyingSource* source)
+    : m_source(source)
     , m_isStarted(false)
     , m_isDraining(false)
     , m_isPulling(false)
     , m_isSchedulingPull(false)
     , m_state(Waiting)
-    , m_wait(new WaitPromise(scriptState->executionContext(), this, WaitPromise::Ready))
-    , m_closed(new ClosedPromise(scriptState->executionContext(), this, ClosedPromise::Closed))
+    , m_wait(new WaitPromise(executionContext, this, WaitPromise::Ready))
+    , m_closed(new ClosedPromise(executionContext, this, ClosedPromise::Closed))
 {
     ScriptWrappable::init(this);
-
-    ScriptPromise promise = source->startSource(exceptionState);
-    // The underlying source calls |this->error| on failure.
-    promise.then(adoptPtr(new OnStarted(scriptState->isolate(), this)));
 }
 
 ReadableStream::~ReadableStream()
 {
 }
 
+String ReadableStream::stateString() const
+{
+    switch (m_state) {
+    case Readable:
+        return "readable";
+    case Waiting:
+        return "waiting";
+    case Closed:
+        return "closed";
+    case Errored:
+        return "errored";
+    }
+    ASSERT(false);
+    return String();
+}
+
 bool ReadableStream::enqueuePreliminaryCheck(size_t chunkSize)
 {
     if (m_state == Errored || m_state == Closed || m_isDraining)
@@ -88,18 +84,18 @@
     }
 }
 
-void ReadableStream::readPreliminaryCheck(ExceptionState* exceptionState)
+void ReadableStream::readPreliminaryCheck(ExceptionState& exceptionState)
 {
     if (m_state == Waiting) {
-        exceptionState->throwTypeError("read is called while state is waiting");
+        exceptionState.throwTypeError("read is called while state is waiting");
         return;
     }
     if (m_state == Closed) {
-        exceptionState->throwTypeError("read is called while state is closed");
+        exceptionState.throwTypeError("read is called while state is closed");
         return;
     }
     if (m_state == Errored) {
-        exceptionState->throwDOMException(m_exception->code(), m_exception->message());
+        exceptionState.throwDOMException(m_exception->code(), m_exception->message());
         return;
     }
 }
@@ -174,7 +170,7 @@
     }
 }
 
-void ReadableStream::onStarted()
+void ReadableStream::didSourceStart()
 {
     m_isStarted = true;
     if (m_isSchedulingPull)
diff --git a/Source/core/streams/ReadableStream.h b/Source/core/streams/ReadableStream.h
index df0c937..722eccf 100644
--- a/Source/core/streams/ReadableStream.h
+++ b/Source/core/streams/ReadableStream.h
@@ -23,7 +23,7 @@
 class ExceptionState;
 class UnderlyingSource;
 
-class ReadableStream : public GarbageCollectedFinalized<ReadableStream>, public ScriptWrappable, public ContextLifecycleObserver {
+class ReadableStream : public GarbageCollectedFinalized<ReadableStream>, public ScriptWrappable {
 public:
     enum State {
         Readable,
@@ -34,15 +34,19 @@
 
     // FIXME: Define Strategy here.
     // FIXME: Add |strategy| constructor parameter.
-    ReadableStream(ScriptState*, UnderlyingSource*, ExceptionState*);
+    // After ReadableStream construction, |didSourceStart| must be called when
+    // |source| initialization succeeds and |error| must be called when
+    // |source| initialization fails.
+    ReadableStream(ExecutionContext*, UnderlyingSource* /* source */);
     virtual ~ReadableStream();
 
     bool isStarted() const { return m_isStarted; }
     bool isDraining() const { return m_isDraining; }
     bool isPulling() const { return m_isPulling; }
     State state() const { return m_state; }
+    String stateString() const;
 
-    virtual ScriptValue read(ScriptState*, ExceptionState*) = 0;
+    virtual ScriptValue read(ScriptState*, ExceptionState&) = 0;
     ScriptPromise wait(ScriptState*);
     ScriptPromise cancel(ScriptState*, ScriptValue reason);
     ScriptPromise closed(ScriptState*);
@@ -50,23 +54,23 @@
     void close();
     void error(PassRefPtrWillBeRawPtr<DOMException>);
 
+    void didSourceStart();
+
     virtual void trace(Visitor*);
 
 protected:
     bool enqueuePreliminaryCheck(size_t chunkSize);
     bool enqueuePostAction(size_t totalQueueSize);
-    void readPreliminaryCheck(ExceptionState*);
+    void readPreliminaryCheck(ExceptionState&);
     void readPostAction();
 
 private:
-    class OnStarted;
     typedef ScriptPromiseProperty<Member<ReadableStream>, V8UndefinedType, RefPtrWillBeMember<DOMException> > WaitPromise;
     typedef ScriptPromiseProperty<Member<ReadableStream>, V8UndefinedType, RefPtrWillBeMember<DOMException> > ClosedPromise;
 
     virtual bool isQueueEmpty() const = 0;
     virtual void clearQueue() = 0;
 
-    void onStarted(void);
     void callOrSchedulePull();
 
     Member<UnderlyingSource> m_source;
diff --git a/Source/core/streams/ReadableStream.idl b/Source/core/streams/ReadableStream.idl
index 927fa94..9d299d2 100644
--- a/Source/core/streams/ReadableStream.idl
+++ b/Source/core/streams/ReadableStream.idl
@@ -2,8 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+enum ReadableStreamState {
+    "readable",
+    "waiting",
+    "closed",
+    "errored"
+};
+
 [
     RuntimeEnabled=Stream,
     GarbageCollected
 ] interface ReadableStream {
+    [CallWith=ScriptState, RaisesException] any read();
+    [CallWith=ScriptState] Promise wait();
+    [ImplementedAs=stateString] readonly attribute ReadableStreamState state;
+
+    [CallWith=ScriptState] Promise cancel(any reason);
+
+    [CallWith=ScriptState] readonly attribute Promise closed;
 };
diff --git a/Source/core/streams/ReadableStreamImpl.h b/Source/core/streams/ReadableStreamImpl.h
index e1a7dfa..b6060b4 100644
--- a/Source/core/streams/ReadableStreamImpl.h
+++ b/Source/core/streams/ReadableStreamImpl.h
@@ -42,7 +42,7 @@
     typedef RefPtr<ArrayBuffer> HoldType;
     typedef PassRefPtr<ArrayBuffer> PassType;
 
-    static size_t size(PassType value) { return value->byteLength(); }
+    static size_t size(const PassType& value) { return value->byteLength(); }
     static size_t size(const HoldType& value) { return value->byteLength(); }
     static ScriptValue toScriptValue(ScriptState* scriptState, const HoldType& value)
     {
@@ -56,13 +56,13 @@
 template <typename ChunkTypeTraits>
 class ReadableStreamImpl : public ReadableStream {
 public:
-    ReadableStreamImpl(ScriptState* scriptState, UnderlyingSource* source, ExceptionState* exceptionState)
-        : ReadableStream(scriptState, source, exceptionState)
+    ReadableStreamImpl(ExecutionContext* executionContext, UnderlyingSource* source)
+        : ReadableStream(executionContext, source)
         , m_totalQueueSize(0) { }
     virtual ~ReadableStreamImpl() { }
 
     // ReadableStream methods
-    virtual ScriptValue read(ScriptState*, ExceptionState*) OVERRIDE;
+    virtual ScriptValue read(ScriptState*, ExceptionState&) OVERRIDE;
 
     bool enqueue(typename ChunkTypeTraits::PassType);
 
@@ -96,10 +96,10 @@
 }
 
 template <typename ChunkTypeTraits>
-ScriptValue ReadableStreamImpl<ChunkTypeTraits>::read(ScriptState* scriptState, ExceptionState* exceptionState)
+ScriptValue ReadableStreamImpl<ChunkTypeTraits>::read(ScriptState* scriptState, ExceptionState& exceptionState)
 {
     readPreliminaryCheck(exceptionState);
-    if (exceptionState->hadException())
+    if (exceptionState.hadException())
         return ScriptValue();
     ASSERT(state() == Readable);
     ASSERT(!m_queue.isEmpty());
diff --git a/Source/core/streams/ReadableStreamTest.cpp b/Source/core/streams/ReadableStreamTest.cpp
index edbc0c9..d87182f 100644
--- a/Source/core/streams/ReadableStreamTest.cpp
+++ b/Source/core/streams/ReadableStreamTest.cpp
@@ -49,11 +49,11 @@
     String* m_value;
 };
 
-class MockUnderlyingSource : public UnderlyingSource {
+class MockUnderlyingSource : public GarbageCollectedFinalized<MockUnderlyingSource>, public UnderlyingSource {
+    USING_GARBAGE_COLLECTED_MIXIN(MockUnderlyingSource);
 public:
     virtual ~MockUnderlyingSource() { }
 
-    MOCK_METHOD1(startSource, ScriptPromise(ExceptionState*));
     MOCK_METHOD0(pullSource, void());
     MOCK_METHOD2(cancelSource, ScriptPromise(ScriptState*, ScriptValue));
 };
@@ -96,16 +96,10 @@
         return StringCapturingFunction::create(isolate(), value);
     }
 
-    // Note: This function calls RunMicrotasks.
     StringStream* construct()
     {
-        RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState());
-        ScriptPromise promise = resolver->promise();
-        resolver->resolve();
-        EXPECT_CALL(*m_underlyingSource, startSource(&m_exceptionState)).WillOnce(Return(promise));
-
-        StringStream* stream = new StringStream(scriptState(), m_underlyingSource, &m_exceptionState);
-        isolate()->RunMicrotasks();
+        StringStream* stream = new StringStream(scriptState()->executionContext(), m_underlyingSource);
+        stream->didSourceStart();
         return stream;
     }
 
@@ -115,27 +109,16 @@
     ExceptionState m_exceptionState;
 };
 
-TEST_F(ReadableStreamTest, Construct)
+TEST_F(ReadableStreamTest, Start)
 {
-    RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState());
-    ScriptPromise promise = resolver->promise();
-    {
-        InSequence s;
-        EXPECT_CALL(*m_underlyingSource, startSource(&m_exceptionState)).WillOnce(Return(promise));
-    }
-    StringStream* stream = new StringStream(scriptState(), m_underlyingSource, &m_exceptionState);
+    StringStream* stream = new StringStream(scriptState()->executionContext(), m_underlyingSource);
     EXPECT_FALSE(m_exceptionState.hadException());
     EXPECT_FALSE(stream->isStarted());
     EXPECT_FALSE(stream->isDraining());
     EXPECT_FALSE(stream->isPulling());
     EXPECT_EQ(stream->state(), ReadableStream::Waiting);
 
-    isolate()->RunMicrotasks();
-
-    EXPECT_FALSE(stream->isStarted());
-
-    resolver->resolve();
-    isolate()->RunMicrotasks();
+    stream->didSourceStart();
 
     EXPECT_TRUE(stream->isStarted());
     EXPECT_FALSE(stream->isDraining());
@@ -143,42 +126,16 @@
     EXPECT_EQ(stream->state(), ReadableStream::Waiting);
 }
 
-TEST_F(ReadableStreamTest, ConstructError)
+TEST_F(ReadableStreamTest, StartFail)
 {
-    {
-        InSequence s;
-        EXPECT_CALL(*m_underlyingSource, startSource(&m_exceptionState))
-            .WillOnce(DoAll(Invoke(ThrowError("hello")), Return(ScriptPromise())));
-    }
-    new StringStream(scriptState(), m_underlyingSource, &m_exceptionState);
-    EXPECT_TRUE(m_exceptionState.hadException());
-}
-
-TEST_F(ReadableStreamTest, StartFailAsynchronously)
-{
-    RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState());
-    ScriptPromise promise = resolver->promise();
-    {
-        InSequence s;
-        EXPECT_CALL(*m_underlyingSource, startSource(&m_exceptionState)).WillOnce(Return(promise));
-    }
-    StringStream* stream = new StringStream(scriptState(), m_underlyingSource, &m_exceptionState);
+    StringStream* stream = new StringStream(scriptState()->executionContext(), m_underlyingSource);
     EXPECT_FALSE(m_exceptionState.hadException());
     EXPECT_FALSE(stream->isStarted());
     EXPECT_FALSE(stream->isDraining());
     EXPECT_FALSE(stream->isPulling());
     EXPECT_EQ(stream->state(), ReadableStream::Waiting);
 
-    isolate()->RunMicrotasks();
-
-    EXPECT_FALSE(stream->isStarted());
-    EXPECT_FALSE(stream->isDraining());
-    EXPECT_FALSE(stream->isPulling());
-    EXPECT_EQ(stream->state(), ReadableStream::Waiting);
-
-    resolver->reject();
     stream->error(DOMException::create(NotFoundError));
-    isolate()->RunMicrotasks();
 
     EXPECT_FALSE(stream->isStarted());
     EXPECT_FALSE(stream->isDraining());
@@ -214,13 +171,7 @@
 
 TEST_F(ReadableStreamTest, WaitDuringStarting)
 {
-    RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState());
-    ScriptPromise promise = resolver->promise();
-    {
-        InSequence s;
-        EXPECT_CALL(*m_underlyingSource, startSource(&m_exceptionState)).WillOnce(Return(promise));
-    }
-    StringStream* stream = new StringStream(scriptState(), m_underlyingSource, &m_exceptionState);
+    StringStream* stream = new StringStream(scriptState()->executionContext(), m_underlyingSource);
     Checkpoint checkpoint;
 
     EXPECT_EQ(ReadableStream::Waiting, stream->state());
@@ -240,8 +191,7 @@
 
     EXPECT_TRUE(stream->isPulling());
 
-    resolver->resolve();
-    isolate()->RunMicrotasks();
+    stream->didSourceStart();
 
     EXPECT_EQ(ReadableStream::Waiting, stream->state());
     EXPECT_TRUE(stream->isPulling());
@@ -442,7 +392,7 @@
     EXPECT_EQ(ReadableStream::Waiting, stream->state());
     EXPECT_FALSE(m_exceptionState.hadException());
 
-    stream->read(scriptState(), &m_exceptionState);
+    stream->read(scriptState(), m_exceptionState);
     EXPECT_EQ(ReadableStream::Waiting, stream->state());
     EXPECT_TRUE(m_exceptionState.hadException());
     EXPECT_EQ(V8TypeError, m_exceptionState.code());
@@ -457,7 +407,7 @@
     EXPECT_EQ(ReadableStream::Closed, stream->state());
     EXPECT_FALSE(m_exceptionState.hadException());
 
-    stream->read(scriptState(), &m_exceptionState);
+    stream->read(scriptState(), m_exceptionState);
     EXPECT_EQ(ReadableStream::Closed, stream->state());
     EXPECT_TRUE(m_exceptionState.hadException());
     EXPECT_EQ(V8TypeError, m_exceptionState.code());
@@ -475,7 +425,7 @@
     EXPECT_EQ(ReadableStream::Errored, stream->state());
     EXPECT_FALSE(m_exceptionState.hadException());
 
-    stream->read(scriptState(), &m_exceptionState);
+    stream->read(scriptState(), m_exceptionState);
     EXPECT_EQ(ReadableStream::Errored, stream->state());
     EXPECT_TRUE(m_exceptionState.hadException());
     EXPECT_EQ(notFoundExceptionCode, m_exceptionState.code());
@@ -502,7 +452,7 @@
 
     checkpoint.Call(0);
     String chunk;
-    EXPECT_TRUE(stream->read(scriptState(), &m_exceptionState).toString(chunk));
+    EXPECT_TRUE(stream->read(scriptState(), m_exceptionState).toString(chunk));
     checkpoint.Call(1);
     EXPECT_FALSE(m_exceptionState.hadException());
     EXPECT_EQ("hello", chunk);
@@ -537,7 +487,7 @@
 
     checkpoint.Call(0);
     String chunk;
-    EXPECT_TRUE(stream->read(scriptState(), &m_exceptionState).toString(chunk));
+    EXPECT_TRUE(stream->read(scriptState(), m_exceptionState).toString(chunk));
     checkpoint.Call(1);
     EXPECT_FALSE(m_exceptionState.hadException());
     EXPECT_EQ("hello", chunk);
@@ -567,7 +517,7 @@
     EXPECT_TRUE(stream->isDraining());
 
     String chunk;
-    EXPECT_TRUE(stream->read(scriptState(), &m_exceptionState).toString(chunk));
+    EXPECT_TRUE(stream->read(scriptState(), m_exceptionState).toString(chunk));
     EXPECT_EQ("hello", chunk);
     EXPECT_EQ(promise, stream->wait(scriptState()));
 
@@ -577,7 +527,7 @@
     EXPECT_FALSE(stream->isPulling());
     EXPECT_TRUE(stream->isDraining());
 
-    EXPECT_TRUE(stream->read(scriptState(), &m_exceptionState).toString(chunk));
+    EXPECT_TRUE(stream->read(scriptState(), m_exceptionState).toString(chunk));
     EXPECT_EQ("bye", chunk);
     EXPECT_FALSE(m_exceptionState.hadException());
 
@@ -698,12 +648,7 @@
 TEST_F(ReadableStreamTest, ReadableArrayBufferCompileTest)
 {
     // This test tests if ReadableStreamImpl<ArrayBuffer> can be instantiated.
-    {
-        InSequence s;
-        EXPECT_CALL(*m_underlyingSource, startSource(&m_exceptionState)).WillOnce(Return(ScriptPromise()));
-    }
-
-    new ReadableStreamImpl<ReadableStreamChunkTypeTraits<ArrayBuffer> >(scriptState(), m_underlyingSource, &m_exceptionState);
+    new ReadableStreamImpl<ReadableStreamChunkTypeTraits<ArrayBuffer> >(scriptState()->executionContext(), m_underlyingSource);
 }
 
 } // namespace blink
diff --git a/Source/core/streams/UnderlyingSource.h b/Source/core/streams/UnderlyingSource.h
index f741175..3f025e4 100644
--- a/Source/core/streams/UnderlyingSource.h
+++ b/Source/core/streams/UnderlyingSource.h
@@ -5,6 +5,7 @@
 #ifndef UnderlyingSource_h
 #define UnderlyingSource_h
 
+#include "bindings/core/v8/ScriptPromise.h"
 #include "bindings/core/v8/ScriptValue.h"
 #include "platform/heap/Heap.h"
 
@@ -13,13 +14,10 @@
 class ExceptionState;
 class ScriptState;
 
-class UnderlyingSource : public GarbageCollectedFinalized<UnderlyingSource> {
+class UnderlyingSource : public GarbageCollectedMixin {
 public:
     virtual ~UnderlyingSource() { }
 
-    // When startSource fails asynchronously, it must call
-    // ReadableStream::error with a DOM exception.
-    virtual ScriptPromise startSource(ExceptionState*) = 0;
     virtual void pullSource() = 0;
     virtual ScriptPromise cancelSource(ScriptState*, ScriptValue reason) = 0;
     virtual void trace(Visitor*) { }
diff --git a/Source/core/xml/XMLHttpRequest.cpp b/Source/core/xml/XMLHttpRequest.cpp
index 30c5016..d263355 100644
--- a/Source/core/xml/XMLHttpRequest.cpp
+++ b/Source/core/xml/XMLHttpRequest.cpp
@@ -26,6 +26,7 @@
 #include "bindings/core/v8/ExceptionState.h"
 #include "core/FetchInitiatorTypeNames.h"
 #include "core/dom/ContextFeatures.h"
+#include "core/dom/DOMException.h"
 #include "core/dom/DOMImplementation.h"
 #include "core/dom/ExceptionCode.h"
 #include "core/dom/XMLDocument.h"
@@ -44,7 +45,10 @@
 #include "core/inspector/InspectorInstrumentation.h"
 #include "core/inspector/InspectorTraceEvents.h"
 #include "core/loader/ThreadableLoader.h"
+#include "core/streams/ReadableStream.h"
+#include "core/streams/ReadableStreamImpl.h"
 #include "core/streams/Stream.h"
+#include "core/streams/UnderlyingSource.h"
 #include "core/xml/XMLHttpRequestProgressEvent.h"
 #include "core/xml/XMLHttpRequestUpload.h"
 #include "platform/Logging.h"
@@ -111,6 +115,34 @@
     context->addConsoleMessage(ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, message));
 }
 
+namespace {
+
+class ReadableStreamSource : public GarbageCollectedFinalized<ReadableStreamSource>, public UnderlyingSource {
+    USING_GARBAGE_COLLECTED_MIXIN(ReadableStreamSource);
+public:
+    ReadableStreamSource(XMLHttpRequest* owner) : m_owner(owner) { }
+    virtual ~ReadableStreamSource() { }
+    virtual void pullSource() OVERRIDE { }
+    virtual ScriptPromise cancelSource(ScriptState* scriptState, ScriptValue reason) OVERRIDE
+    {
+        m_owner->abort();
+        return ScriptPromise::cast(scriptState, v8::Undefined(scriptState->isolate()));
+    }
+    virtual void trace(Visitor* visitor) OVERRIDE
+    {
+        visitor->trace(m_owner);
+        UnderlyingSource::trace(visitor);
+    }
+
+private:
+    // This is RawPtr in non-oilpan build to avoid the reference cycle. To
+    // avoid use-after free, the associated ReadableStream must be closed
+    // or errored when m_owner is gone.
+    RawPtrWillBeMember<XMLHttpRequest> m_owner;
+};
+
+} // namespace
+
 PassRefPtrWillBeRawPtr<XMLHttpRequest> XMLHttpRequest::create(ExecutionContext* context, PassRefPtr<SecurityOrigin> securityOrigin)
 {
     RefPtrWillBeRawPtr<XMLHttpRequest> xmlHttpRequest = adoptRefWillBeRefCountedGarbageCollected(new XMLHttpRequest(context, securityOrigin));
@@ -295,14 +327,23 @@
     return m_responseArrayBuffer.get();
 }
 
-Stream* XMLHttpRequest::responseStream()
+Stream* XMLHttpRequest::responseLegacyStream()
 {
     ASSERT(m_responseTypeCode == ResponseTypeLegacyStream);
 
     if (m_error || (m_state != LOADING && m_state != DONE))
         return 0;
 
-    return m_responseStream.get();
+    return m_responseLegacyStream.get();
+}
+
+ReadableStream* XMLHttpRequest::responseStream()
+{
+    ASSERT(m_responseTypeCode == ResponseTypeStream);
+    if (m_error || (m_state != LOADING && m_state != DONE))
+        return 0;
+
+    return m_responseStream;
 }
 
 void XMLHttpRequest::setTimeout(unsigned long timeout, ExceptionState& exceptionState)
@@ -356,6 +397,11 @@
             m_responseTypeCode = ResponseTypeLegacyStream;
         else
             return;
+    } else if (responseType == "stream") {
+        if (RuntimeEnabledFeatures::streamEnabled())
+            m_responseTypeCode = ResponseTypeStream;
+        else
+            return;
     } else {
         ASSERT_NOT_REACHED();
     }
@@ -378,6 +424,8 @@
         return "arraybuffer";
     case ResponseTypeLegacyStream:
         return "legacystream";
+    case ResponseTypeStream:
+        return "stream";
     }
     return "";
 }
@@ -893,8 +941,15 @@
 
     InspectorInstrumentation::didFailXHRLoading(executionContext(), this, this);
 
-    if (m_responseStream && m_state != DONE)
-        m_responseStream->abort();
+    if (m_responseLegacyStream && m_state != DONE)
+        m_responseLegacyStream->abort();
+
+    if (m_responseStream) {
+        // When the stream is already closed (including canceled from the
+        // user), |error| does nothing.
+        // FIXME: Create a more specific error.
+        m_responseStream->error(DOMException::create(!m_async && m_exceptionCode ? m_exceptionCode : AbortError, "XMLHttpRequest::abort"));
+    }
 
     if (!m_loader)
         return true;
@@ -936,6 +991,7 @@
     m_responseBlob = nullptr;
     m_downloadedBlobLength = 0;
 
+    m_responseLegacyStream = nullptr;
     m_responseStream = nullptr;
 
     // These variables may referred by the response accessors. So, we can clear
@@ -1229,8 +1285,11 @@
     if (m_decoder)
         m_responseText = m_responseText.concatenateWith(m_decoder->flush());
 
+    if (m_responseLegacyStream)
+        m_responseLegacyStream->finalize();
+
     if (m_responseStream)
-        m_responseStream->finalize();
+        m_responseStream->close();
 
     clearVariablesForLoading();
 
@@ -1326,9 +1385,15 @@
             m_binaryResponseBuilder = SharedBuffer::create();
         m_binaryResponseBuilder->append(data, len);
     } else if (m_responseTypeCode == ResponseTypeLegacyStream) {
-        if (!m_responseStream)
-            m_responseStream = Stream::create(executionContext(), finalResponseMIMEType());
-        m_responseStream->addData(data, len);
+        if (!m_responseLegacyStream)
+            m_responseLegacyStream = Stream::create(executionContext(), responseType());
+        m_responseLegacyStream->addData(data, len);
+    } else if (m_responseTypeCode == ResponseTypeStream) {
+        if (!m_responseStream) {
+            m_responseStream = new ReadableStreamImpl<ReadableStreamChunkTypeTraits<ArrayBuffer> >(executionContext(), new ReadableStreamSource(this));
+            m_responseStream->didSourceStart();
+        }
+        m_responseStream->enqueue(ArrayBuffer::create(data, len));
     }
 
     if (m_error)
@@ -1417,7 +1482,9 @@
 void XMLHttpRequest::trace(Visitor* visitor)
 {
     visitor->trace(m_responseBlob);
+    visitor->trace(m_responseLegacyStream);
     visitor->trace(m_responseStream);
+    visitor->trace(m_streamSource);
     visitor->trace(m_responseDocument);
     visitor->trace(m_progressEventThrottle);
     visitor->trace(m_upload);
diff --git a/Source/core/xml/XMLHttpRequest.h b/Source/core/xml/XMLHttpRequest.h
index f15701f..2e6455e 100644
--- a/Source/core/xml/XMLHttpRequest.h
+++ b/Source/core/xml/XMLHttpRequest.h
@@ -26,6 +26,7 @@
 #include "core/dom/ActiveDOMObject.h"
 #include "core/events/EventListener.h"
 #include "core/loader/ThreadableLoaderClient.h"
+#include "core/streams/ReadableStreamImpl.h"
 #include "core/xml/XMLHttpRequestEventTarget.h"
 #include "core/xml/XMLHttpRequestProgressEventThrottle.h"
 #include "platform/heap/Handle.h"
@@ -48,6 +49,7 @@
 class Stream;
 class TextResourceDecoder;
 class ThreadableLoader;
+class UnderlyingSource;
 
 typedef int ExceptionCode;
 
@@ -79,7 +81,8 @@
         ResponseTypeDocument,
         ResponseTypeBlob,
         ResponseTypeArrayBuffer,
-        ResponseTypeLegacyStream
+        ResponseTypeLegacyStream,
+        ResponseTypeStream,
     };
 
     virtual void contextDestroyed() OVERRIDE;
@@ -117,7 +120,8 @@
     ScriptString responseJSONSource();
     Document* responseXML(ExceptionState&);
     Blob* responseBlob();
-    Stream* responseStream();
+    Stream* responseLegacyStream();
+    ReadableStream* responseStream();
     unsigned long timeout() const { return m_timeoutMilliseconds; }
     void setTimeout(unsigned long timeout, ExceptionState&);
 
@@ -230,7 +234,9 @@
     AtomicString m_mimeTypeOverride;
     unsigned long m_timeoutMilliseconds;
     RefPtrWillBeMember<Blob> m_responseBlob;
-    RefPtrWillBeMember<Stream> m_responseStream;
+    RefPtrWillBeMember<Stream> m_responseLegacyStream;
+    PersistentWillBeMember<ReadableStreamImpl<ReadableStreamChunkTypeTraits<ArrayBuffer> > > m_responseStream;
+    PersistentWillBeMember<UnderlyingSource> m_streamSource;
 
     RefPtr<ThreadableLoader> m_loader;
     State m_state;
diff --git a/Source/core/xml/XMLHttpRequest.idl b/Source/core/xml/XMLHttpRequest.idl
index 9493a79..e03fe6b 100644
--- a/Source/core/xml/XMLHttpRequest.idl
+++ b/Source/core/xml/XMLHttpRequest.idl
@@ -33,7 +33,8 @@
     "document",
     "json",
     "text",
-    "legacystream"
+    "legacystream",
+    "stream"
 };
 
 [