blob: b168fb3c8fdd831a2ede69403122a145537d990e [file]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modules/fetch/Body.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "bindings/core/v8/ScriptState.h"
#include "bindings/core/v8/V8ArrayBuffer.h"
#include "bindings/core/v8/V8ThrowException.h"
#include "core/dom/DOMArrayBuffer.h"
#include "core/dom/DOMTypedArray.h"
#include "core/fileapi/Blob.h"
#include "core/frame/UseCounter.h"
#include "modules/fetch/BodyStreamBuffer.h"
#include "modules/fetch/FetchDataLoader.h"
#include "public/platform/WebDataConsumerHandle.h"
#include "wtf/PassRefPtr.h"
#include "wtf/RefPtr.h"
#include <memory>
namespace blink {
namespace {
class BodyConsumerBase : public GarbageCollectedFinalized<BodyConsumerBase>,
public FetchDataLoader::Client {
WTF_MAKE_NONCOPYABLE(BodyConsumerBase);
USING_GARBAGE_COLLECTED_MIXIN(BodyConsumerBase);
public:
explicit BodyConsumerBase(ScriptPromiseResolver* resolver)
: m_resolver(resolver) {}
ScriptPromiseResolver* resolver() { return m_resolver; }
void didFetchDataLoadFailed() override {
ScriptState::Scope scope(resolver()->getScriptState());
m_resolver->reject(V8ThrowException::createTypeError(
resolver()->getScriptState()->isolate(), "Failed to fetch"));
}
DEFINE_INLINE_TRACE() {
visitor->trace(m_resolver);
FetchDataLoader::Client::trace(visitor);
}
private:
Member<ScriptPromiseResolver> m_resolver;
};
class BodyBlobConsumer final : public BodyConsumerBase {
WTF_MAKE_NONCOPYABLE(BodyBlobConsumer);
public:
explicit BodyBlobConsumer(ScriptPromiseResolver* resolver)
: BodyConsumerBase(resolver) {}
void didFetchDataLoadedBlobHandle(
PassRefPtr<BlobDataHandle> blobDataHandle) override {
resolver()->resolve(Blob::create(std::move(blobDataHandle)));
}
};
class BodyArrayBufferConsumer final : public BodyConsumerBase {
WTF_MAKE_NONCOPYABLE(BodyArrayBufferConsumer);
public:
explicit BodyArrayBufferConsumer(ScriptPromiseResolver* resolver)
: BodyConsumerBase(resolver) {}
void didFetchDataLoadedArrayBuffer(DOMArrayBuffer* arrayBuffer) override {
resolver()->resolve(arrayBuffer);
}
};
class BodyTextConsumer final : public BodyConsumerBase {
WTF_MAKE_NONCOPYABLE(BodyTextConsumer);
public:
explicit BodyTextConsumer(ScriptPromiseResolver* resolver)
: BodyConsumerBase(resolver) {}
void didFetchDataLoadedString(const String& string) override {
resolver()->resolve(string);
}
};
class BodyJsonConsumer final : public BodyConsumerBase {
WTF_MAKE_NONCOPYABLE(BodyJsonConsumer);
public:
explicit BodyJsonConsumer(ScriptPromiseResolver* resolver)
: BodyConsumerBase(resolver) {}
void didFetchDataLoadedString(const String& string) override {
if (!resolver()->getExecutionContext() ||
resolver()->getExecutionContext()->isContextDestroyed())
return;
ScriptState::Scope scope(resolver()->getScriptState());
v8::Isolate* isolate = resolver()->getScriptState()->isolate();
v8::Local<v8::String> inputString = v8String(isolate, string);
v8::TryCatch trycatch(isolate);
v8::Local<v8::Value> parsed;
if (v8Call(v8::JSON::Parse(isolate, inputString), parsed, trycatch))
resolver()->resolve(parsed);
else
resolver()->reject(trycatch.Exception());
}
};
} // namespace
ScriptPromise Body::arrayBuffer(ScriptState* scriptState) {
ScriptPromise promise = rejectInvalidConsumption(scriptState);
if (!promise.isEmpty())
return promise;
// When the main thread sends a V8::TerminateExecution() signal to a worker
// thread, any V8 API on the worker thread starts returning an empty
// handle. This can happen in this function. To avoid the situation, we
// first check the ExecutionContext and return immediately if it's already
// gone (which means that the V8::TerminateExecution() signal has been sent
// to this worker thread).
if (!scriptState->getExecutionContext())
return ScriptPromise();
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
promise = resolver->promise();
if (bodyBuffer()) {
bodyBuffer()->startLoading(FetchDataLoader::createLoaderAsArrayBuffer(),
new BodyArrayBufferConsumer(resolver));
} else {
resolver->resolve(DOMArrayBuffer::create(0u, 1));
}
return promise;
}
ScriptPromise Body::blob(ScriptState* scriptState) {
ScriptPromise promise = rejectInvalidConsumption(scriptState);
if (!promise.isEmpty())
return promise;
// See above comment.
if (!scriptState->getExecutionContext())
return ScriptPromise();
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
promise = resolver->promise();
if (bodyBuffer()) {
bodyBuffer()->startLoading(
FetchDataLoader::createLoaderAsBlobHandle(mimeType()),
new BodyBlobConsumer(resolver));
} else {
std::unique_ptr<BlobData> blobData = BlobData::create();
blobData->setContentType(mimeType());
resolver->resolve(
Blob::create(BlobDataHandle::create(std::move(blobData), 0)));
}
return promise;
}
ScriptPromise Body::json(ScriptState* scriptState) {
ScriptPromise promise = rejectInvalidConsumption(scriptState);
if (!promise.isEmpty())
return promise;
// See above comment.
if (!scriptState->getExecutionContext())
return ScriptPromise();
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
promise = resolver->promise();
if (bodyBuffer()) {
bodyBuffer()->startLoading(FetchDataLoader::createLoaderAsString(),
new BodyJsonConsumer(resolver));
} else {
resolver->reject(V8ThrowException::createSyntaxError(
scriptState->isolate(), "Unexpected end of input"));
}
return promise;
}
ScriptPromise Body::text(ScriptState* scriptState) {
ScriptPromise promise = rejectInvalidConsumption(scriptState);
if (!promise.isEmpty())
return promise;
// See above comment.
if (!scriptState->getExecutionContext())
return ScriptPromise();
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
promise = resolver->promise();
if (bodyBuffer()) {
bodyBuffer()->startLoading(FetchDataLoader::createLoaderAsString(),
new BodyTextConsumer(resolver));
} else {
resolver->resolve(String());
}
return promise;
}
ScriptValue Body::bodyWithUseCounter(ScriptState* scriptState) {
UseCounter::count(getExecutionContext(), UseCounter::FetchBodyStream);
if (!bodyBuffer())
return ScriptValue::createNull(scriptState);
ScriptValue stream = bodyBuffer()->stream();
ASSERT(stream.getScriptState() == scriptState);
return stream;
}
bool Body::bodyUsed() {
return bodyBuffer() && bodyBuffer()->isStreamDisturbed();
}
bool Body::isBodyLocked() {
return bodyBuffer() && bodyBuffer()->isStreamLocked();
}
bool Body::hasPendingActivity() const {
if (!getExecutionContext() || getExecutionContext()->isContextDestroyed())
return false;
if (!bodyBuffer())
return false;
return bodyBuffer()->hasPendingActivity();
}
Body::Body(ExecutionContext* context) : ContextClient(context) {}
ScriptPromise Body::rejectInvalidConsumption(ScriptState* scriptState) {
if (isBodyLocked() || bodyUsed())
return ScriptPromise::reject(
scriptState, V8ThrowException::createTypeError(scriptState->isolate(),
"Already read"));
return ScriptPromise();
}
} // namespace blink