Commit a12e77aa authored by Yoichi Osato's avatar Yoichi Osato Committed by Commit Bot

Reland "[fetch] upload streaming basic implementation under flag"

This CL relands the patch crrev.com/c/2172724, which was reverted
as crrev.com/c/2224218 due to
1. linux-blink-cors-rel failure
2. http/tests/loading/bad-server-subframe.html failing on mac bots

I fixed #1 by updating FlagExpectations/disable-features=OutOfBlinkCors.
For #2, it is reported flaky:
https://analysis.chromium.org/p/chromium/flake-portal/flakes/occurrences?key=ag9zfmZpbmRpdC1mb3ItbWVyTwsSBUZsYWtlIkRjaHJvbWl1bUBibGlua193ZWJfdGVzdHNAaHR0cC90ZXN0cy9sb2FkaW5nL2JhZC1zZXJ2ZXItc3ViZnJhbWUuaHRtbAw
(guessed crrev.com/c/2219088 the culprit). Now this CL passes the bots.

Bug: 688906
Cq-Include-Trybots: chromium/try:mac10.10-blink-rel,mac10.11-blink-rel
Change-Id: I9d64f917014096f79b224369fc719e9743086b45
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2226103Reviewed-by: default avatarYutaka Hirano <yhirano@chromium.org>
Commit-Queue: Yoichi Osato <yoichio@chromium.org>
Cr-Commit-Position: refs/heads/master@{#774418}
parent 11023e7d
...@@ -1124,6 +1124,7 @@ jumbo_source_set("unit_tests") { ...@@ -1124,6 +1124,7 @@ jumbo_source_set("unit_tests") {
"fetch/bytes_consumer_tee_test.cc", "fetch/bytes_consumer_tee_test.cc",
"fetch/bytes_consumer_test_util.cc", "fetch/bytes_consumer_test_util.cc",
"fetch/bytes_consumer_test_util.h", "fetch/bytes_consumer_test_util.h",
"fetch/bytes_uploader_test.cc",
"fetch/fetch_data_loader_test.cc", "fetch/fetch_data_loader_test.cc",
"fetch/fetch_header_list_test.cc", "fetch/fetch_header_list_test.cc",
"fetch/fetch_request_data_test.cc", "fetch/fetch_request_data_test.cc",
......
...@@ -14,6 +14,8 @@ blink_core_sources("fetch") { ...@@ -14,6 +14,8 @@ blink_core_sources("fetch") {
"body_stream_buffer.h", "body_stream_buffer.h",
"bytes_consumer_tee.cc", "bytes_consumer_tee.cc",
"bytes_consumer_tee.h", "bytes_consumer_tee.h",
"bytes_uploader.cc",
"bytes_uploader.h",
"fetch_data_loader.cc", "fetch_data_loader.cc",
"fetch_data_loader.h", "fetch_data_loader.h",
"fetch_header_list.cc", "fetch_header_list.cc",
......
...@@ -3,9 +3,16 @@ include_rules = [ ...@@ -3,9 +3,16 @@ include_rules = [
"+mojo/public/cpp/system/data_pipe.h", "+mojo/public/cpp/system/data_pipe.h",
"+mojo/public/cpp/system/data_pipe_utils.h", "+mojo/public/cpp/system/data_pipe_utils.h",
"+mojo/public/cpp/system/simple_watcher.h", "+mojo/public/cpp/system/simple_watcher.h",
"+net/base/net_errors.h",
"+net/base/request_priority.h", "+net/base/request_priority.h",
"+net/http/http_response_info.h", "+net/http/http_response_info.h",
"+services/network/public/cpp", "+services/network/public/cpp",
"+services/network/public/mojom", "+services/network/public/mojom",
"+url/gurl.h", "+url/gurl.h",
] ]
specific_include_rules = {
"bytes_uploader_test\.cc": [
"+base/test/mock_callback.h",
],
}
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "third_party/blink/renderer/core/execution_context/execution_context.h" #include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/fetch/body.h" #include "third_party/blink/renderer/core/fetch/body.h"
#include "third_party/blink/renderer/core/fetch/bytes_consumer_tee.h" #include "third_party/blink/renderer/core/fetch/bytes_consumer_tee.h"
#include "third_party/blink/renderer/core/fetch/bytes_uploader.h"
#include "third_party/blink/renderer/core/fetch/readable_stream_bytes_consumer.h" #include "third_party/blink/renderer/core/fetch/readable_stream_bytes_consumer.h"
#include "third_party/blink/renderer/core/streams/readable_stream.h" #include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h" #include "third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h"
...@@ -208,6 +209,22 @@ scoped_refptr<EncodedFormData> BodyStreamBuffer::DrainAsFormData( ...@@ -208,6 +209,22 @@ scoped_refptr<EncodedFormData> BodyStreamBuffer::DrainAsFormData(
return nullptr; return nullptr;
} }
void BodyStreamBuffer::DrainAsChunkedDataPipeGetter(
ScriptState* script_state,
mojo::PendingReceiver<network::mojom::blink::ChunkedDataPipeGetter>
pending_receiver,
ExceptionState& exception_state) {
DCHECK(!IsStreamLocked());
auto* consumer = MakeGarbageCollected<ReadableStreamBytesConsumer>(
script_state, stream_, exception_state);
if (exception_state.HadException())
return;
stream_uploader_ = MakeGarbageCollected<BytesUploader>(
consumer, std::move(pending_receiver),
ExecutionContext::From(script_state)
->GetTaskRunner(TaskType::kNetworking));
}
void BodyStreamBuffer::StartLoading(FetchDataLoader* loader, void BodyStreamBuffer::StartLoading(FetchDataLoader* loader,
FetchDataLoader::Client* client, FetchDataLoader::Client* client,
ExceptionState& exception_state) { ExceptionState& exception_state) {
...@@ -381,6 +398,7 @@ scoped_refptr<BlobDataHandle> BodyStreamBuffer::TakeSideDataBlob() { ...@@ -381,6 +398,7 @@ scoped_refptr<BlobDataHandle> BodyStreamBuffer::TakeSideDataBlob() {
void BodyStreamBuffer::Trace(Visitor* visitor) const { void BodyStreamBuffer::Trace(Visitor* visitor) const {
visitor->Trace(script_state_); visitor->Trace(script_state_);
visitor->Trace(stream_); visitor->Trace(stream_);
visitor->Trace(stream_uploader_);
visitor->Trace(consumer_); visitor->Trace(consumer_);
visitor->Trace(loader_); visitor->Trace(loader_);
visitor->Trace(signal_); visitor->Trace(signal_);
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#include <memory> #include <memory>
#include "base/util/type_safety/pass_key.h" #include "base/util/type_safety/pass_key.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h" #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/core_export.h"
...@@ -20,6 +22,7 @@ ...@@ -20,6 +22,7 @@
namespace blink { namespace blink {
class BytesUploader;
class EncodedFormData; class EncodedFormData;
class ExceptionState; class ExceptionState;
class ReadableStream; class ReadableStream;
...@@ -61,6 +64,10 @@ class CORE_EXPORT BodyStreamBuffer final : public UnderlyingSourceBase, ...@@ -61,6 +64,10 @@ class CORE_EXPORT BodyStreamBuffer final : public UnderlyingSourceBase,
BytesConsumer::BlobSizePolicy, BytesConsumer::BlobSizePolicy,
ExceptionState&); ExceptionState&);
scoped_refptr<EncodedFormData> DrainAsFormData(ExceptionState&); scoped_refptr<EncodedFormData> DrainAsFormData(ExceptionState&);
void DrainAsChunkedDataPipeGetter(
ScriptState*,
mojo::PendingReceiver<network::mojom::blink::ChunkedDataPipeGetter>,
ExceptionState&);
void StartLoading(FetchDataLoader*, void StartLoading(FetchDataLoader*,
FetchDataLoader::Client* /* client */, FetchDataLoader::Client* /* client */,
ExceptionState&); ExceptionState&);
...@@ -115,6 +122,7 @@ class CORE_EXPORT BodyStreamBuffer final : public UnderlyingSourceBase, ...@@ -115,6 +122,7 @@ class CORE_EXPORT BodyStreamBuffer final : public UnderlyingSourceBase,
Member<ScriptState> script_state_; Member<ScriptState> script_state_;
Member<ReadableStream> stream_; Member<ReadableStream> stream_;
Member<BytesUploader> stream_uploader_;
Member<BytesConsumer> consumer_; Member<BytesConsumer> consumer_;
// We need this member to keep it alive while loading. // We need this member to keep it alive while loading.
Member<FetchDataLoader> loader_; Member<FetchDataLoader> loader_;
......
// Copyright 2020 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 "third_party/blink/renderer/core/fetch/bytes_uploader.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/single_thread_task_runner.h"
#include "net/base/net_errors.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/loader/fetch/bytes_consumer.h"
namespace blink {
BytesUploader::BytesUploader(
BytesConsumer* consumer,
mojo::PendingReceiver<network::mojom::blink::ChunkedDataPipeGetter>
pending_receiver,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: consumer_(consumer),
receiver_(this, std::move(pending_receiver)),
upload_pipe_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL,
std::move(task_runner)) {
DCHECK(consumer_);
DCHECK_EQ(consumer_->GetPublicState(),
BytesConsumer::PublicState::kReadableOrWaiting);
}
BytesUploader::~BytesUploader() = default;
void BytesUploader::Trace(blink::Visitor* visitor) const {
visitor->Trace(consumer_);
BytesConsumer::Client::Trace(visitor);
}
void BytesUploader::GetSize(GetSizeCallback get_size_callback) {
DCHECK(!get_size_callback_);
get_size_callback_ = std::move(get_size_callback);
}
void BytesUploader::StartReading(
mojo::ScopedDataPipeProducerHandle upload_pipe) {
DVLOG(3) << this << " StartReading()";
DCHECK(get_size_callback_);
DCHECK(upload_pipe);
if (upload_pipe_) {
// Replay was asked by net/ service.
CloseOnError();
return;
}
upload_pipe_ = std::move(upload_pipe);
upload_pipe_watcher_.Watch(upload_pipe_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
WTF::BindRepeating(&BytesUploader::OnPipeWriteable,
WrapWeakPersistent(this)));
consumer_->SetClient(this);
if (consumer_->GetPublicState() ==
BytesConsumer::PublicState::kReadableOrWaiting) {
WriteDataOnPipe();
}
}
void BytesUploader::OnStateChange() {
DVLOG(3) << this << " OnStateChange(). consumer_->GetPublicState()="
<< consumer_->GetPublicState();
DCHECK(get_size_callback_);
switch (consumer_->GetPublicState()) {
case BytesConsumer::PublicState::kReadableOrWaiting:
WriteDataOnPipe();
return;
case BytesConsumer::PublicState::kClosed:
Close();
return;
case BytesConsumer::PublicState::kErrored:
CloseOnError();
return;
}
NOTREACHED();
}
void BytesUploader::OnPipeWriteable(MojoResult unused) {
WriteDataOnPipe();
}
void BytesUploader::WriteDataOnPipe() {
DVLOG(3) << this << " WriteDataOnPipe(). consumer_->GetPublicState()="
<< consumer_->GetPublicState();
DCHECK(upload_pipe_);
DCHECK(get_size_callback_);
if (!upload_pipe_.is_valid())
return;
while (true) {
const char* buffer;
size_t available;
auto consumer_result = consumer_->BeginRead(&buffer, &available);
DVLOG(3) << " consumer_->BeginRead()=" << consumer_result
<< ", available=" << available;
switch (consumer_result) {
case BytesConsumer::Result::kError:
CloseOnError();
return;
case BytesConsumer::Result::kShouldWait:
return;
case BytesConsumer::Result::kDone:
Close();
return;
case BytesConsumer::Result::kOk:
break;
}
DCHECK_EQ(consumer_result, BytesConsumer::Result::kOk);
uint32_t written_bytes = base::saturated_cast<uint32_t>(available);
const MojoResult mojo_result = upload_pipe_->WriteData(
buffer, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE);
DVLOG(3) << " upload_pipe_->WriteData()=" << mojo_result
<< ", mojo_written=" << written_bytes
<< ", consumer_->EndRead()=" << consumer_result;
if (mojo_result == MOJO_RESULT_SHOULD_WAIT) {
// Wait for the pipe to have more capacity available
consumer_result = consumer_->EndRead(0);
upload_pipe_watcher_.ArmOrNotify();
return;
}
if (mojo_result != MOJO_RESULT_OK) {
CloseOnError();
return;
}
consumer_result = consumer_->EndRead(written_bytes);
if (!base::CheckAdd(total_size_, written_bytes)
.AssignIfValid(&total_size_)) {
CloseOnError();
return;
}
switch (consumer_result) {
case BytesConsumer::Result::kError:
CloseOnError();
return;
case BytesConsumer::Result::kShouldWait:
NOTREACHED();
return;
case BytesConsumer::Result::kDone:
Close();
return;
case BytesConsumer::Result::kOk:
break;
}
}
}
void BytesUploader::Close() {
DVLOG(3) << this << " Close(). total_size=" << total_size_;
DCHECK(get_size_callback_);
std::move(get_size_callback_).Run(net::OK, total_size_);
}
void BytesUploader::CloseOnError() {
DVLOG(3) << this << " CloseOnError(). total_size=" << total_size_;
DCHECK(consumer_);
consumer_->Cancel();
DCHECK(get_size_callback_);
std::move(get_size_callback_).Run(net::ERR_FAILED, total_size_);
}
} // namespace blink
// Copyright 2020 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FETCH_BYTES_UPLOADER_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_FETCH_BYTES_UPLOADER_H_
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom-blink.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/loader/fetch/bytes_consumer.h"
namespace base {
class SingleThreadTaskRunner;
} // namespace base
namespace blink {
class CORE_EXPORT BytesUploader
: public GarbageCollected<BytesUploader>,
public BytesConsumer::Client,
public network::mojom::blink::ChunkedDataPipeGetter {
USING_GARBAGE_COLLECTED_MIXIN(BytesUploader);
public:
BytesUploader(
BytesConsumer* consumer,
mojo::PendingReceiver<network::mojom::blink::ChunkedDataPipeGetter>
pending_receiver,
scoped_refptr<base::SingleThreadTaskRunner> task_runner);
~BytesUploader() override;
BytesUploader(const BytesUploader&) = delete;
BytesUploader& operator=(const BytesUploader&) = delete;
void Trace(blink::Visitor*) const override;
private:
// BytesConsumer::Client implementation
void OnStateChange() override;
String DebugName() const override { return "BytesUploader"; }
// mojom::ChunkedDataPipeGetter implementation:
void GetSize(GetSizeCallback get_size_callback) override;
void StartReading(mojo::ScopedDataPipeProducerHandle upload_pipe) override;
void OnPipeWriteable(MojoResult unused);
void WriteDataOnPipe();
void Close();
// TODO(yoichio): Add a string parameter and show it on console.
void CloseOnError();
Member<BytesConsumer> consumer_;
mojo::Receiver<network::mojom::blink::ChunkedDataPipeGetter> receiver_;
mojo::ScopedDataPipeProducerHandle upload_pipe_;
mojo::SimpleWatcher upload_pipe_watcher_;
GetSizeCallback get_size_callback_;
uint64_t total_size_ = 0;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FETCH_BYTES_UPLOADER_H_
// Copyright 2020 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 "third_party/blink/renderer/core/fetch/bytes_uploader.h"
#include "base/test/mock_callback.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/net_errors.h"
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom-blink.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
using network::mojom::blink::ChunkedDataPipeGetter;
using testing::_;
using testing::InSequence;
using testing::Invoke;
using testing::Return;
namespace blink {
typedef testing::StrictMock<testing::MockFunction<void(int)>> Checkpoint;
class MockBytesConsumer : public BytesConsumer {
public:
MockBytesConsumer() = default;
~MockBytesConsumer() override = default;
MOCK_METHOD2(BeginRead, Result(const char**, size_t*));
MOCK_METHOD1(EndRead, Result(size_t));
MOCK_METHOD1(SetClient, void(Client*));
MOCK_METHOD0(ClearClient, void());
MOCK_METHOD0(Cancel, void());
MOCK_CONST_METHOD0(GetPublicState, PublicState());
MOCK_CONST_METHOD0(GetError, Error());
MOCK_CONST_METHOD0(DebugName, String());
};
class BytesUploaderTest : public ::testing::Test {
public:
void InitializeBytesUploader(uint32_t capacity = 100u) {
mock_bytes_consumer_ = MakeGarbageCollected<MockBytesConsumer>();
EXPECT_CALL(*mock_bytes_consumer_, GetPublicState())
.WillRepeatedly(Return(BytesConsumer::PublicState::kReadableOrWaiting));
bytes_uploader_ = MakeGarbageCollected<BytesUploader>(
mock_bytes_consumer_, remote_.BindNewPipeAndPassReceiver(),
Thread::Current()->GetTaskRunner());
const MojoCreateDataPipeOptions data_pipe_options{
sizeof(MojoCreateDataPipeOptions), MOJO_CREATE_DATA_PIPE_FLAG_NONE, 1,
capacity};
ASSERT_EQ(MOJO_RESULT_OK,
mojo::CreateDataPipe(&data_pipe_options, &writable_, &readable_));
}
MockBytesConsumer& Mock() const { return *mock_bytes_consumer_; }
mojo::ScopedDataPipeProducerHandle& Writable() { return writable_; }
mojo::ScopedDataPipeConsumerHandle& Readable() { return readable_; }
mojo::Remote<ChunkedDataPipeGetter>& Remote() { return remote_; }
private:
Persistent<BytesUploader> bytes_uploader_;
Persistent<MockBytesConsumer> mock_bytes_consumer_;
mojo::ScopedDataPipeProducerHandle writable_;
mojo::ScopedDataPipeConsumerHandle readable_;
mojo::Remote<ChunkedDataPipeGetter> remote_;
};
TEST_F(BytesUploaderTest, Create) {
MockBytesConsumer* mock_bytes_consumer =
MakeGarbageCollected<MockBytesConsumer>();
Checkpoint checkpoint;
{
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(*mock_bytes_consumer, GetPublicState())
.WillRepeatedly(Return(BytesConsumer::PublicState::kReadableOrWaiting));
}
checkpoint.Call(1);
mojo::PendingRemote<ChunkedDataPipeGetter> pending_remote;
BytesUploader* bytes_uploader_ = MakeGarbageCollected<BytesUploader>(
mock_bytes_consumer, pending_remote.InitWithNewPipeAndPassReceiver(),
Thread::Current()->GetTaskRunner());
ASSERT_TRUE(bytes_uploader_);
}
// TODO(yoichio): Needs BytesConsumer state tests.
TEST_F(BytesUploaderTest, ReadEmpty) {
InitializeBytesUploader();
base::MockCallback<ChunkedDataPipeGetter::GetSizeCallback> get_size_callback;
Checkpoint checkpoint;
{
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(Mock(), SetClient(_));
EXPECT_CALL(Mock(), GetPublicState())
.WillRepeatedly(Return(BytesConsumer::PublicState::kReadableOrWaiting));
EXPECT_CALL(Mock(), BeginRead(_, _))
.WillOnce(Return(BytesConsumer::Result::kDone));
EXPECT_CALL(get_size_callback, Run(net::OK, 0u));
EXPECT_CALL(checkpoint, Call(3));
}
checkpoint.Call(1);
Remote()->GetSize(get_size_callback.Get());
Remote()->StartReading(std::move(Writable()));
checkpoint.Call(2);
test::RunPendingTasks();
checkpoint.Call(3);
char buffer[20] = {};
uint32_t num_bytes = sizeof(buffer);
MojoResult rv =
Readable()->ReadData(buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, rv);
}
TEST_F(BytesUploaderTest, ReadSmall) {
InitializeBytesUploader();
base::MockCallback<ChunkedDataPipeGetter::GetSizeCallback> get_size_callback;
Checkpoint checkpoint;
{
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(Mock(), SetClient(_));
EXPECT_CALL(Mock(), GetPublicState())
.WillRepeatedly(Return(BytesConsumer::PublicState::kReadableOrWaiting));
EXPECT_CALL(Mock(), BeginRead(_, _))
.WillOnce(Invoke([](const char** buffer, size_t* size) {
*size = 6;
*buffer = "foobar";
return BytesConsumer::Result::kOk;
}));
EXPECT_CALL(Mock(), EndRead(6u))
.WillOnce(Return(BytesConsumer::Result::kDone));
EXPECT_CALL(get_size_callback, Run(net::OK, 6u));
EXPECT_CALL(checkpoint, Call(3));
}
checkpoint.Call(1);
Remote()->GetSize(get_size_callback.Get());
Remote()->StartReading(std::move(Writable()));
checkpoint.Call(2);
test::RunPendingTasks();
checkpoint.Call(3);
char buffer[20] = {};
uint32_t num_bytes = sizeof(buffer);
EXPECT_EQ(MOJO_RESULT_OK,
Readable()->ReadData(buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE));
EXPECT_EQ(6u, num_bytes);
EXPECT_STREQ("foobar", buffer);
}
TEST_F(BytesUploaderTest, ReadOverPipeCapacity) {
InitializeBytesUploader(10u);
base::MockCallback<ChunkedDataPipeGetter::GetSizeCallback> get_size_callback;
Checkpoint checkpoint;
{
InSequence s;
EXPECT_CALL(checkpoint, Call(1));
EXPECT_CALL(checkpoint, Call(2));
EXPECT_CALL(Mock(), SetClient(_));
EXPECT_CALL(Mock(), GetPublicState())
.WillRepeatedly(Return(BytesConsumer::PublicState::kReadableOrWaiting));
EXPECT_CALL(Mock(), BeginRead(_, _))
.WillOnce(Invoke([](const char** buffer, size_t* size) {
*size = 12;
*buffer = "foobarFOOBAR";
return BytesConsumer::Result::kOk;
}));
EXPECT_CALL(Mock(), EndRead(10u))
.WillOnce(Return(BytesConsumer::Result::kOk));
EXPECT_CALL(Mock(), BeginRead(_, _))
.WillOnce(Invoke([](const char** buffer, size_t* size) {
*size = 2;
*buffer = "AR";
return BytesConsumer::Result::kOk;
}));
EXPECT_CALL(Mock(), EndRead(0u))
.WillOnce(Return(BytesConsumer::Result::kOk));
EXPECT_CALL(checkpoint, Call(3));
EXPECT_CALL(checkpoint, Call(4));
EXPECT_CALL(Mock(), BeginRead(_, _))
.WillOnce(Invoke([](const char** buffer, size_t* size) {
*size = 2;
*buffer = "AR";
return BytesConsumer::Result::kOk;
}));
EXPECT_CALL(Mock(), EndRead(2u))
.WillOnce(Return(BytesConsumer::Result::kDone));
EXPECT_CALL(get_size_callback, Run(net::OK, 12u));
}
checkpoint.Call(1);
Remote()->GetSize(get_size_callback.Get());
Remote()->StartReading(std::move(Writable()));
checkpoint.Call(2);
test::RunPendingTasks();
checkpoint.Call(3);
char buffer[20] = {};
uint32_t num_bytes = sizeof(buffer);
EXPECT_EQ(MOJO_RESULT_OK,
Readable()->ReadData(buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE));
EXPECT_EQ(10u, num_bytes);
EXPECT_STREQ("foobarFOOB", buffer);
checkpoint.Call(4);
test::RunPendingTasks();
char buffer2[20] = {};
num_bytes = sizeof(buffer2);
EXPECT_EQ(MOJO_RESULT_OK, Readable()->ReadData(buffer2, &num_bytes,
MOJO_READ_DATA_FLAG_NONE));
EXPECT_EQ(2u, num_bytes);
EXPECT_STREQ("AR", buffer2);
}
} // namespace blink
...@@ -715,10 +715,25 @@ void FetchManager::Loader::PerformHTTPFetch(ExceptionState& exception_state) { ...@@ -715,10 +715,25 @@ void FetchManager::Loader::PerformHTTPFetch(ExceptionState& exception_state) {
if (fetch_request_data_->Method() != http_names::kGET && if (fetch_request_data_->Method() != http_names::kGET &&
fetch_request_data_->Method() != http_names::kHEAD) { fetch_request_data_->Method() != http_names::kHEAD) {
if (fetch_request_data_->Buffer()) { if (fetch_request_data_->Buffer()) {
request.SetHttpBody( scoped_refptr<EncodedFormData> form_data =
fetch_request_data_->Buffer()->DrainAsFormData(exception_state)); fetch_request_data_->Buffer()->DrainAsFormData(exception_state);
if (exception_state.HadException()) if (exception_state.HadException())
return; return;
if (form_data) {
request.SetHttpBody(form_data);
} else if (RuntimeEnabledFeatures::OutOfBlinkCorsEnabled() &&
RuntimeEnabledFeatures::FetchUploadStreamingEnabled(
execution_context_)) {
mojo::PendingRemote<network::mojom::blink::ChunkedDataPipeGetter>
pending_remote;
fetch_request_data_->Buffer()->DrainAsChunkedDataPipeGetter(
resolver_->GetScriptState(),
pending_remote.InitWithNewPipeAndPassReceiver(), exception_state);
if (exception_state.HadException())
return;
request.MutableBody().SetStreamBody(std::move(pending_remote));
}
} }
} }
request.SetCacheMode(fetch_request_data_->CacheMode()); request.SetCacheMode(fetch_request_data_->CacheMode());
......
...@@ -175,6 +175,14 @@ static BodyStreamBuffer* ExtractBody(ScriptState* script_state, ...@@ -175,6 +175,14 @@ static BodyStreamBuffer* ExtractBody(ScriptState* script_state,
execution_context, std::move(form_data)), execution_context, std::move(form_data)),
nullptr /* AbortSignal */); nullptr /* AbortSignal */);
content_type = "application/x-www-form-urlencoded;charset=UTF-8"; content_type = "application/x-www-form-urlencoded;charset=UTF-8";
} else if (RuntimeEnabledFeatures::OutOfBlinkCorsEnabled() &&
RuntimeEnabledFeatures::FetchUploadStreamingEnabled(
execution_context) &&
V8ReadableStream::HasInstance(body, isolate)) {
ReadableStream* readable_stream =
V8ReadableStream::ToImpl(body.As<v8::Object>());
return_buffer =
MakeGarbageCollected<BodyStreamBuffer>(script_state, readable_stream);
} else { } else {
String string = NativeValueTraits<IDLUSVString>::NativeValue( String string = NativeValueTraits<IDLUSVString>::NativeValue(
isolate, body, exception_state); isolate, body, exception_state);
......
...@@ -66,4 +66,32 @@ BytesConsumer* BytesConsumer::CreateClosed() { ...@@ -66,4 +66,32 @@ BytesConsumer* BytesConsumer::CreateClosed() {
return MakeGarbageCollected<ClosedBytesConsumer>(); return MakeGarbageCollected<ClosedBytesConsumer>();
} }
std::ostream& operator<<(std::ostream& out,
const BytesConsumer::PublicState& state) {
switch (state) {
case BytesConsumer::PublicState::kReadableOrWaiting:
return out << "kReadableOrWaiting";
case BytesConsumer::PublicState::kClosed:
return out << "kClosed";
case BytesConsumer::PublicState::kErrored:
return out << "kErrored";
}
NOTREACHED();
}
std::ostream& operator<<(std::ostream& out,
const BytesConsumer::Result& result) {
switch (result) {
case BytesConsumer::Result::kOk:
return out << "kOk";
case BytesConsumer::Result::kShouldWait:
return out << "kShouldWait";
case BytesConsumer::Result::kDone:
return out << "kDone";
case BytesConsumer::Result::kError:
return out << "kError";
}
NOTREACHED();
}
} // namespace blink } // namespace blink
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_BYTES_CONSUMER_H_ #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_BYTES_CONSUMER_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_BYTES_CONSUMER_H_ #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_BYTES_CONSUMER_H_
#include <ostream>
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "third_party/blink/renderer/platform/blob/blob_data.h" #include "third_party/blink/renderer/platform/blob/blob_data.h"
#include "third_party/blink/renderer/platform/heap/handle.h" #include "third_party/blink/renderer/platform/heap/handle.h"
...@@ -204,6 +206,12 @@ class PLATFORM_EXPORT BytesConsumer : public GarbageCollected<BytesConsumer> { ...@@ -204,6 +206,12 @@ class PLATFORM_EXPORT BytesConsumer : public GarbageCollected<BytesConsumer> {
} }
}; };
PLATFORM_EXPORT std::ostream& operator<<(
std::ostream& out,
const BytesConsumer::PublicState& state);
PLATFORM_EXPORT std::ostream& operator<<(std::ostream& out,
const BytesConsumer::Result& result);
} // namespace blink } // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_BYTES_CONSUMER_H_ #endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_BYTES_CONSUMER_H_
...@@ -97,15 +97,20 @@ ResourceRequestBody::ResourceRequestBody( ...@@ -97,15 +97,20 @@ ResourceRequestBody::ResourceRequestBody(
: form_body_(form_body) {} : form_body_(form_body) {}
ResourceRequestBody::ResourceRequestBody(ResourceRequestBody&& src) ResourceRequestBody::ResourceRequestBody(ResourceRequestBody&& src)
: ResourceRequestBody(std::move(src.form_body_)) {} : form_body_(std::move(src.form_body_)),
stream_body_(std::move(src.stream_body_)) {}
ResourceRequestBody& ResourceRequestBody::operator=(ResourceRequestBody&& src) { ResourceRequestBody& ResourceRequestBody::operator=(ResourceRequestBody&& src) =
form_body_ = std::move(src.form_body_); default;
return *this;
}
ResourceRequestBody::~ResourceRequestBody() = default; ResourceRequestBody::~ResourceRequestBody() = default;
void ResourceRequestBody::SetStreamBody(
mojo::PendingRemote<network::mojom::blink::ChunkedDataPipeGetter>
stream_body) {
stream_body_ = std::move(stream_body);
}
ResourceRequest::ResourceRequest() : ResourceRequestHead(NullURL()) {} ResourceRequest::ResourceRequest() : ResourceRequestHead(NullURL()) {}
ResourceRequest::ResourceRequest(const String& url_string) ResourceRequest::ResourceRequest(const String& url_string)
...@@ -117,6 +122,8 @@ ResourceRequest::ResourceRequest(const ResourceRequestHead& head) ...@@ -117,6 +122,8 @@ ResourceRequest::ResourceRequest(const ResourceRequestHead& head)
: ResourceRequestHead(head) {} : ResourceRequestHead(head) {}
ResourceRequest& ResourceRequest::operator=(const ResourceRequest& src) { ResourceRequest& ResourceRequest::operator=(const ResourceRequest& src) {
DCHECK(!body_.StreamBody().is_valid());
DCHECK(!src.body_.StreamBody().is_valid());
this->ResourceRequestHead::operator=(src); this->ResourceRequestHead::operator=(src);
body_.SetFormBody(src.body_.FormBody()); body_.SetFormBody(src.body_.FormBody());
return *this; return *this;
...@@ -129,6 +136,8 @@ ResourceRequest& ResourceRequest::operator=(ResourceRequest&&) = default; ...@@ -129,6 +136,8 @@ ResourceRequest& ResourceRequest::operator=(ResourceRequest&&) = default;
ResourceRequest::~ResourceRequest() = default; ResourceRequest::~ResourceRequest() = default;
void ResourceRequest::CopyFrom(const ResourceRequest& src) { void ResourceRequest::CopyFrom(const ResourceRequest& src) {
DCHECK(!body_.StreamBody().is_valid());
DCHECK(!src.body_.StreamBody().is_valid());
*this = src; *this = src;
} }
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include "base/unguessable_token.h" #include "base/unguessable_token.h"
#include "net/cookies/site_for_cookies.h" #include "net/cookies/site_for_cookies.h"
#include "services/metrics/public/cpp/ukm_source_id.h" #include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom-blink.h"
#include "services/network/public/mojom/cors.mojom-blink-forward.h" #include "services/network/public/mojom/cors.mojom-blink-forward.h"
#include "services/network/public/mojom/fetch_api.mojom-blink-forward.h" #include "services/network/public/mojom/fetch_api.mojom-blink-forward.h"
#include "services/network/public/mojom/ip_address_space.mojom-blink-forward.h" #include "services/network/public/mojom/ip_address_space.mojom-blink-forward.h"
...@@ -554,8 +555,21 @@ class PLATFORM_EXPORT ResourceRequestBody { ...@@ -554,8 +555,21 @@ class PLATFORM_EXPORT ResourceRequestBody {
const scoped_refptr<EncodedFormData>& FormBody() const { return form_body_; } const scoped_refptr<EncodedFormData>& FormBody() const { return form_body_; }
void SetFormBody(scoped_refptr<EncodedFormData>); void SetFormBody(scoped_refptr<EncodedFormData>);
mojo::PendingRemote<network::mojom::blink::ChunkedDataPipeGetter>
TakeStreamBody() {
return std::move(stream_body_);
}
const mojo::PendingRemote<network::mojom::blink::ChunkedDataPipeGetter>&
StreamBody() const {
return stream_body_;
}
void SetStreamBody(
mojo::PendingRemote<network::mojom::blink::ChunkedDataPipeGetter>);
private: private:
scoped_refptr<EncodedFormData> form_body_; scoped_refptr<EncodedFormData> form_body_;
mojo::PendingRemote<network::mojom::blink::ChunkedDataPipeGetter>
stream_body_;
}; };
// A ResourceRequest is a "request" object for ResourceLoader. Conceptually // A ResourceRequest is a "request" object for ResourceLoader. Conceptually
......
...@@ -347,6 +347,15 @@ void PopulateResourceRequest(const ResourceRequestHead& src, ...@@ -347,6 +347,15 @@ void PopulateResourceRequest(const ResourceRequestHead& src,
dest->request_body = base::MakeRefCounted<network::ResourceRequestBody>(); dest->request_body = base::MakeRefCounted<network::ResourceRequestBody>();
PopulateResourceRequestBody(*body, dest->request_body.get()); PopulateResourceRequestBody(*body, dest->request_body.get());
} else if (src_body.StreamBody().is_valid()) {
DCHECK_NE(dest->method, net::HttpRequestHeaders::kGetMethod);
DCHECK_NE(dest->method, net::HttpRequestHeaders::kHeadMethod);
mojo::PendingRemote<network::mojom::blink::ChunkedDataPipeGetter>
stream_body = src_body.TakeStreamBody();
dest->request_body = base::MakeRefCounted<network::ResourceRequestBody>();
mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter>
network_stream_body(stream_body.PassPipe(), 0u);
dest->request_body->SetToChunkedDataPipe(std::move(network_stream_body));
} }
if (resource_type == mojom::ResourceType::kStylesheet) { if (resource_type == mojom::ResourceType::kStylesheet) {
......
...@@ -757,6 +757,10 @@ ...@@ -757,6 +757,10 @@
{ {
name: "FeaturePolicyVibrateFeature" name: "FeaturePolicyVibrateFeature"
}, },
{
name: "FetchUploadStreaming",
status: "test",
},
{ {
// Also enabled when blink::features::kFileHandlingAPI is overridden // Also enabled when blink::features::kFileHandlingAPI is overridden
// on the command line (or via chrome://flags). // on the command line (or via chrome://flags).
......
...@@ -84,3 +84,12 @@ crbug.com/941297 http/tests/inspector-protocol/network/xhr-cors-preflight-redire ...@@ -84,3 +84,12 @@ crbug.com/941297 http/tests/inspector-protocol/network/xhr-cors-preflight-redire
# Added by https://chromium-review.googlesource.com/c/chromium/src/+/2178572 and # Added by https://chromium-review.googlesource.com/c/chromium/src/+/2178572 and
# Blink CORS does not support. # Blink CORS does not support.
crbug.com/911952 external/wpt/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https.html [ Skip ] crbug.com/911952 external/wpt/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https.html [ Skip ]
# Fetch upload streaming is only supported with OOR-CORS.
crbug.com/688906 http/tests/fetch/serviceworker/fetch_upload-base-https-other-https.html [ Failure ]
crbug.com/688906 http/tests/fetch/serviceworker/fetch_upload.html [ Failure ]
crbug.com/688906 http/tests/fetch/window/fetch_upload-base-https-other-https.html [ Failure ]
crbug.com/688906 http/tests/fetch/window/fetch_upload.html [ Failure ]
crbug.com/688906 http/tests/fetch/workers/fetch_upload-base-https-other-https.html [ Failure ]
crbug.com/688906 http/tests/fetch/workers/fetch_upload.html [ Failure ]
crbug.com/688906 external/wpt/fetch/api/basic/request-upload.any.js [ Failure ]
...@@ -10,11 +10,11 @@ PASS Fetch with POST with Float32Array body ...@@ -10,11 +10,11 @@ PASS Fetch with POST with Float32Array body
PASS Fetch with POST with Float64Array body PASS Fetch with POST with Float64Array body
PASS Fetch with POST with DataView body PASS Fetch with POST with DataView body
PASS Fetch with POST with Blob body with mime type PASS Fetch with POST with Blob body with mime type
FAIL Fetch with POST with ReadableStream assert_equals: expected "Test" but got "[object ReadableStream]" FAIL Fetch with POST with ReadableStream assert_equals: expected "Test" but got ""
FAIL Fetch with POST with ReadableStream containing String assert_unreached: Should have rejected: undefined Reached unreachable code PASS Fetch with POST with ReadableStream containing String
FAIL Fetch with POST with ReadableStream containing null assert_unreached: Should have rejected: undefined Reached unreachable code PASS Fetch with POST with ReadableStream containing null
FAIL Fetch with POST with ReadableStream containing number assert_unreached: Should have rejected: undefined Reached unreachable code PASS Fetch with POST with ReadableStream containing number
FAIL Fetch with POST with ReadableStream containing ArrayBuffer assert_unreached: Should have rejected: undefined Reached unreachable code PASS Fetch with POST with ReadableStream containing ArrayBuffer
FAIL Fetch with POST with ReadableStream containing Blob assert_unreached: Should have rejected: undefined Reached unreachable code PASS Fetch with POST with ReadableStream containing Blob
Harness: the test ran to completion. Harness: the test ran to completion.
...@@ -10,11 +10,11 @@ PASS Fetch with POST with Float32Array body ...@@ -10,11 +10,11 @@ PASS Fetch with POST with Float32Array body
PASS Fetch with POST with Float64Array body PASS Fetch with POST with Float64Array body
PASS Fetch with POST with DataView body PASS Fetch with POST with DataView body
PASS Fetch with POST with Blob body with mime type PASS Fetch with POST with Blob body with mime type
FAIL Fetch with POST with ReadableStream assert_equals: expected "Test" but got "[object ReadableStream]" FAIL Fetch with POST with ReadableStream assert_equals: expected "Test" but got ""
FAIL Fetch with POST with ReadableStream containing String assert_unreached: Should have rejected: undefined Reached unreachable code PASS Fetch with POST with ReadableStream containing String
FAIL Fetch with POST with ReadableStream containing null assert_unreached: Should have rejected: undefined Reached unreachable code PASS Fetch with POST with ReadableStream containing null
FAIL Fetch with POST with ReadableStream containing number assert_unreached: Should have rejected: undefined Reached unreachable code PASS Fetch with POST with ReadableStream containing number
FAIL Fetch with POST with ReadableStream containing ArrayBuffer assert_unreached: Should have rejected: undefined Reached unreachable code PASS Fetch with POST with ReadableStream containing ArrayBuffer
FAIL Fetch with POST with ReadableStream containing Blob assert_unreached: Should have rejected: undefined Reached unreachable code PASS Fetch with POST with ReadableStream containing Blob
Harness: the test ran to completion. Harness: the test ran to completion.
if (self.importScripts) {
importScripts('../resources/fetch-test-helpers.js');
}
function fetch_echo(stream) {
return fetch(
'/serviceworker/resources/fetch-echo-body.php',
{method: 'POST', body: stream});
}
function fetch_echo_body(stream) {
return fetch_echo(stream).then(response => response.text());
}
function create_stream(contents) {
return new ReadableStream({
start: controller => {
for (obj of contents) {
controller.enqueue(obj);
}
controller.close();
}
});
}
promise_test(() => {
const stream =
create_stream(['Foo', 'Bar']).pipeThrough(new TextEncoderStream());
return fetch_echo_body(stream).then(function(text) {
assert_equals(text, 'FooBar');
});
}, 'Fetch with ReadableStream body with Uint8Array');
promise_test(() => {
const stream = create_stream([new Uint8Array(0)]);
return fetch_echo_body(stream).then(function(text) {
assert_equals(text, '');
});
}, 'Fetch with ReadableStream body with empty Uint8Array');
function random_values_array(length) {
const array = new Uint8Array(length);
// crypto.getRandomValues has a quota. See
// https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues.
const cryptoQuota = 65535;
let index = 0;
const buffer = array.buffer;
while (index < buffer.byteLength) {
const bufferView = array.subarray(index, index + cryptoQuota);
crypto.getRandomValues(bufferView);
index += cryptoQuota;
}
return array;
}
function hash256(array) {
return crypto.subtle.digest('SHA-256', array);
}
promise_test(async () => {
const length = 1000 * 1000; // 1Mbytes
const array = random_values_array(length);
const stream = create_stream([array]);
const response = await fetch_echo(stream);
const reader = response.body.getReader();
let index = 0;
while (index < length) {
const chunk = await reader.read();
assert_false(chunk.done);
const chunk_length = chunk.value.length;
const chunk_hash = await hash256(chunk.value);
const src_hash = await hash256(array.subarray(index, index + chunk_length));
assert_array_equals(
new Uint8Array(chunk_hash), new Uint8Array(src_hash),
`Array of [${index}, ${index + length - 1}] should be same.`);
index += chunk_length;
}
const final_chunk = await reader.read();
assert_true(final_chunk.done);
}, 'Fetch with ReadableStream body with long Uint8Array');
promise_test(async (t) => {
const stream = create_stream(['Foobar']);
await promise_rejects_js(t, TypeError, fetch_echo(stream));
}, 'Fetch with ReadableStream body containing String should fail');
promise_test(async (t) => {
const stream = create_stream([null]);
await promise_rejects_js(t, TypeError, fetch_echo(stream));
}, 'Fetch with ReadableStream body containing null should fail');
promise_test(async (t) => {
const stream = create_stream([42]);
await promise_rejects_js(t, TypeError, fetch_echo(stream));
}, 'Fetch with ReadableStream body containing number should fail');
done();
<!doctype html>
<head>
<meta name="timeout" content="long"></meta>
<script src = "/resources/get-host-info.js?pipe=sub"></script>
<script src = "/resources/testharness.js"></script>
<script src = "/resources/testharnessreport.js"></script>
<script src = "/fetch/resources/fetch-test-options.js"></script>
<script src = "/serviceworker/resources/test-helpers.js"></script>
</head>
<body>
<script>
var options = get_fetch_test_options();
function start(t) {
service_worker_test(
'../script-tests/fetch_upload.js?-base-https-other-https',
'fetch_upload-serviceworker');
t.done();
}
function init() {
return Promise.resolve();
}
</script>
<script src = "../resources/init.js"></script>
</body>
<!doctype html>
<head>
<meta name="timeout" content="long"></meta>
<script src = "/resources/get-host-info.js?pipe=sub"></script>
<script src = "/resources/testharness.js"></script>
<script src = "/resources/testharnessreport.js"></script>
<script src = "/fetch/resources/fetch-test-options.js"></script>
<script src = "/serviceworker/resources/test-helpers.js"></script>
</head>
<body>
<script>
var options = get_fetch_test_options();
function start(t) {
service_worker_test(
'../script-tests/fetch_upload.js?',
'fetch_upload-serviceworker');
t.done();
}
function init() {
return Promise.resolve();
}
</script>
<script src = "../resources/init.js"></script>
</body>
<!doctype html>
<head>
<meta name="timeout" content="long"></meta>
<script src = "/resources/get-host-info.js?pipe=sub"></script>
<script src = "/resources/testharness.js"></script>
<script src = "/resources/testharnessreport.js"></script>
<script src = "/serviceworker/resources/test-helpers.js"></script>
<script src = "/fetch/resources/fetch-test-options.js"></script>
<script src = "/fetch/resources/fetch-test-helpers.js"></script>
<script src = "/streams/resources/rs-utils.js"></script>
</head>
<body>
<script>
var options = get_fetch_test_options();
function start(t) {
var script = document.createElement('script');
script.src = '../script-tests/fetch_upload.js?-base-https-other-https';
script.addEventListener(
'error',
function() { unreached_rejection(t); });
script.addEventListener(
'load',
function() { t.done(); });
document.body.appendChild(script);
}
function init() {
return Promise.resolve();
}
</script>
<script src = "../resources/init.js"></script>
</body>
<!doctype html>
<head>
<meta name="timeout" content="long"></meta>
<script src = "/resources/get-host-info.js?pipe=sub"></script>
<script src = "/resources/testharness.js"></script>
<script src = "/resources/testharnessreport.js"></script>
<script src = "/serviceworker/resources/test-helpers.js"></script>
<script src = "/fetch/resources/fetch-test-options.js"></script>
<script src = "/fetch/resources/fetch-test-helpers.js"></script>
<script src = "/streams/resources/rs-utils.js"></script>
</head>
<body>
<script>
var options = get_fetch_test_options();
function start(t) {
var script = document.createElement('script');
script.src = '../script-tests/fetch_upload.js?';
script.addEventListener(
'error',
function() { unreached_rejection(t); });
script.addEventListener(
'load',
function() { t.done(); });
document.body.appendChild(script);
}
function init() {
return Promise.resolve();
}
</script>
<script src = "../resources/init.js"></script>
</body>
<!doctype html>
<head>
<meta name="timeout" content="long"></meta>
<script src = "/resources/get-host-info.js?pipe=sub"></script>
<script src = "/resources/testharness.js"></script>
<script src = "/resources/testharnessreport.js"></script>
<script src = "/fetch/resources/fetch-test-options.js"></script>
<script src = "/serviceworker/resources/test-helpers.js"></script>
</head>
<body>
<script>
var options = get_fetch_test_options();
function start(t) {
fetch_tests_from_worker(
new Worker('../script-tests/fetch_upload.js?-base-https-other-https'));
t.done();
}
function init() {
return Promise.resolve();
}
</script>
<script src = "../resources/init.js"></script>
</body>
<!doctype html>
<head>
<meta name="timeout" content="long"></meta>
<script src = "/resources/get-host-info.js?pipe=sub"></script>
<script src = "/resources/testharness.js"></script>
<script src = "/resources/testharnessreport.js"></script>
<script src = "/fetch/resources/fetch-test-options.js"></script>
<script src = "/serviceworker/resources/test-helpers.js"></script>
</head>
<body>
<script>
var options = get_fetch_test_options();
function start(t) {
fetch_tests_from_worker(
new Worker('../script-tests/fetch_upload.js?'));
t.done();
}
function init() {
return Promise.resolve();
}
</script>
<script src = "../resources/init.js"></script>
</body>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment