Commit 5e4f0f6a authored by Makoto Shimazu's avatar Makoto Shimazu Committed by Commit Bot

ServiceWorker: Implement SWInstalledScriptsSender

This is 7th patch of script streaming project split off from
https://chromium-review.googlesource.com/c/538477.
Design doc:
https://docs.google.com/document/d/1BbETxB2K1GeGUv4XIvGGforAJRRgYSNX5x4vPTGsCPU/edit

This patch implements SWInstalledScriptsSender to push the installed scripts
over mojo pipes from the browser to the renderer. The sender is started when
ServiceWorkerVersion asks EmbeddedWorkerInstance to start the worker. That
means that scripts will be received on the io thread immediately after binding
the Mojo interface.

From this patch, "--enable-features=ServiceWorkerScriptStreaming" takes effect.
When LayoutTests under virtual/service-worker-script-streaming fail, please add
TestExpectations and report it to https://crbug.com/683037.

Bug: 683037
Change-Id: I5aef2cff6026ae3afa004932f77064f24ba8bfc7
Reviewed-on: https://chromium-review.googlesource.com/563144
Commit-Queue: Makoto Shimazu <shimazu@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarTom Sepez <tsepez@chromium.org>
Reviewed-by: default avatarMatt Falkenhagen <falken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#488576}
parent ec0a0ebf
...@@ -4,20 +4,380 @@ ...@@ -4,20 +4,380 @@
#include "content/browser/service_worker/service_worker_installed_scripts_sender.h" #include "content/browser/service_worker/service_worker_installed_scripts_sender.h"
#include "base/memory/ref_counted.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_disk_cache.h"
#include "content/browser/service_worker/service_worker_script_cache_map.h"
#include "content/browser/service_worker/service_worker_storage.h"
#include "content/common/net_adapters.h"
#include "net/http/http_response_headers.h"
namespace content { namespace content {
ServiceWorkerInstalledScriptsSender::ServiceWorkerInstalledScriptsSender() = namespace {
default;
ServiceWorkerInstalledScriptsSender::~ServiceWorkerInstalledScriptsSender() = class MetaDataSender {
default; public:
enum class Status { kSuccess, kFailed };
MetaDataSender(scoped_refptr<net::IOBufferWithSize> meta_data,
mojo::ScopedDataPipeProducerHandle handle)
: meta_data_(std::move(meta_data)),
remaining_bytes_(meta_data_->size()),
handle_(std::move(handle)),
watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC),
weak_factory_(this) {}
void Start(base::OnceCallback<void(Status)> callback) {
callback_ = std::move(callback);
watcher_.Watch(
handle_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
base::Bind(&MetaDataSender::OnWritable, weak_factory_.GetWeakPtr()));
}
void OnWritable(MojoResult result) {
DCHECK_EQ(MOJO_RESULT_OK, result);
uint32_t size = remaining_bytes_;
MojoResult rv = mojo::WriteDataRaw(handle_.get(), meta_data_->data(), &size,
MOJO_WRITE_DATA_FLAG_NONE);
switch (rv) {
case MOJO_RESULT_INVALID_ARGUMENT:
case MOJO_RESULT_OUT_OF_RANGE:
case MOJO_RESULT_BUSY:
NOTREACHED();
return;
case MOJO_RESULT_FAILED_PRECONDITION:
OnCompleted(Status::kFailed);
return;
case MOJO_RESULT_SHOULD_WAIT:
return;
}
remaining_bytes_ -= size;
if (remaining_bytes_ == 0)
OnCompleted(Status::kSuccess);
}
void OnCompleted(Status status) {
watcher_.Cancel();
handle_.reset();
std::move(callback_).Run(status);
}
private:
base::OnceCallback<void(Status)> callback_;
scoped_refptr<net::IOBufferWithSize> meta_data_;
size_t remaining_bytes_;
mojo::ScopedDataPipeProducerHandle handle_;
mojo::SimpleWatcher watcher_;
base::WeakPtrFactory<MetaDataSender> weak_factory_;
};
} // namespace
// Sender sends a single script to the renderer and calls
// ServiceWorkerIsntalledScriptsSender::OnFinishSendingScript() when done.
class ServiceWorkerInstalledScriptsSender::Sender {
public:
Sender(std::unique_ptr<ServiceWorkerResponseReader> reader,
ServiceWorkerInstalledScriptsSender* owner)
: reader_(std::move(reader)),
owner_(owner),
watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
weak_factory_(this) {}
void Start() {
scoped_refptr<HttpResponseInfoIOBuffer> info_buf =
base::MakeRefCounted<HttpResponseInfoIOBuffer>();
reader_->ReadInfo(info_buf.get(), base::Bind(&Sender::OnReadInfoComplete,
AsWeakPtr(), info_buf));
}
private:
void OnReadInfoComplete(scoped_refptr<HttpResponseInfoIOBuffer> http_info,
int result) {
DCHECK(owner_);
DCHECK(http_info);
DCHECK_GT(result, 0);
if (!http_info->http_info) {
CompleteSendIfNeeded(Status::kNoHttpInfoError);
return;
}
mojo::ScopedDataPipeConsumerHandle meta_data_consumer;
mojo::ScopedDataPipeConsumerHandle body_consumer;
if (mojo::CreateDataPipe(nullptr, &body_handle_, &body_consumer) !=
MOJO_RESULT_OK) {
CompleteSendIfNeeded(Status::kCreateDataPipeError);
return;
}
// Start sending meta data (V8 code cache data).
if (http_info->http_info->metadata) {
mojo::ScopedDataPipeProducerHandle meta_data_producer;
if (mojo::CreateDataPipe(nullptr, &meta_data_producer,
&meta_data_consumer) != MOJO_RESULT_OK) {
CompleteSendIfNeeded(Status::kCreateDataPipeError);
return;
}
meta_data_sender_ = base::MakeUnique<MetaDataSender>(
http_info->http_info->metadata, std::move(meta_data_producer));
meta_data_sender_->Start(
base::BindOnce(&Sender::OnMetaDataSent, AsWeakPtr()));
}
// Start sending body.
watcher_.Watch(body_handle_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
base::Bind(&Sender::OnWritableBody, AsWeakPtr()));
watcher_.ArmOrNotify();
scoped_refptr<net::HttpResponseHeaders> headers =
http_info->http_info->headers;
DCHECK(headers);
std::string charset;
headers->GetCharset(&charset);
// Create a map of response headers.
std::unordered_map<std::string, std::string> header_strings;
size_t iter = 0;
std::string key;
std::string value;
// This logic is copied from blink::ResourceResponse::AddHTTPHeaderField.
while (headers->EnumerateHeaderLines(&iter, &key, &value)) {
if (header_strings.find(key) == header_strings.end()) {
header_strings[key] = value;
} else {
header_strings[key] += ", " + value;
}
}
owner_->SendScriptInfoToRenderer(charset, std::move(header_strings),
std::move(meta_data_consumer),
std::move(body_consumer));
owner_->OnHttpInfoRead(http_info);
}
void OnWritableBody(MojoResult result) {
DCHECK_EQ(MOJO_RESULT_OK, result);
DCHECK(!pending_write_);
uint32_t num_bytes = 0;
MojoResult rv = NetToMojoPendingBuffer::BeginWrite(
&body_handle_, &pending_write_, &num_bytes);
switch (rv) {
case MOJO_RESULT_INVALID_ARGUMENT:
case MOJO_RESULT_BUSY:
NOTREACHED();
return;
case MOJO_RESULT_FAILED_PRECONDITION:
CompleteSendIfNeeded(Status::kConnectionError);
return;
case MOJO_RESULT_SHOULD_WAIT:
watcher_.ArmOrNotify();
return;
case MOJO_RESULT_OK:
break;
}
scoped_refptr<NetToMojoIOBuffer> buffer =
base::MakeRefCounted<NetToMojoIOBuffer>(pending_write_.get());
reader_->ReadData(buffer.get(), num_bytes,
base::Bind(&Sender::OnResponseDataRead, AsWeakPtr()));
}
void OnResponseDataRead(int read_bytes) {
if (read_bytes < 0) {
CompleteSendIfNeeded(Status::kResponseReaderError);
return;
}
body_handle_ = pending_write_->Complete(read_bytes);
DCHECK(body_handle_.is_valid());
pending_write_ = nullptr;
if (read_bytes == 0) {
// All data has been read.
watcher_.Cancel();
body_handle_.reset();
CompleteSendIfNeeded(Status::kSuccess);
return;
}
watcher_.ArmOrNotify();
}
void OnMetaDataSent(MetaDataSender::Status status) {
meta_data_sender_.reset();
if (status != MetaDataSender::Status::kSuccess) {
watcher_.Cancel();
body_handle_.reset();
CompleteSendIfNeeded(Status::kMetaDataSenderError);
return;
}
CompleteSendIfNeeded(Status::kSuccess);
}
// CompleteSendIfNeeded notifies the end of data transfer to |owner_|, and
// |this| will be removed by |owner_| as a result. Errors are notified
// immediately, but when the transfer has been succeeded, it's notified when
// sending both of body and meta data is finished.
void CompleteSendIfNeeded(Status status) {
if (status != Status::kSuccess) {
owner_->OnAbortSendingScript(status);
return;
}
if (!body_handle_.is_valid() && !meta_data_sender_)
owner_->OnFinishSendingScript();
}
base::WeakPtr<Sender> AsWeakPtr() { return weak_factory_.GetWeakPtr(); }
std::unique_ptr<ServiceWorkerResponseReader> reader_;
ServiceWorkerInstalledScriptsSender* owner_;
// For meta data.
std::unique_ptr<MetaDataSender> meta_data_sender_;
// For body.
scoped_refptr<NetToMojoPendingBuffer> pending_write_;
mojo::SimpleWatcher watcher_;
// Pipes.
mojo::ScopedDataPipeProducerHandle meta_data_handle_;
mojo::ScopedDataPipeProducerHandle body_handle_;
base::WeakPtrFactory<Sender> weak_factory_;
};
ServiceWorkerInstalledScriptsSender::ServiceWorkerInstalledScriptsSender(
ServiceWorkerVersion* owner,
const GURL& main_script_url,
base::WeakPtr<ServiceWorkerContextCore> context)
: owner_(owner),
main_script_url_(main_script_url),
main_script_id_(kInvalidServiceWorkerResourceId),
state_(State::kNotStarted),
context_(std::move(context)) {}
ServiceWorkerInstalledScriptsSender::~ServiceWorkerInstalledScriptsSender() {}
mojom::ServiceWorkerInstalledScriptsInfoPtr mojom::ServiceWorkerInstalledScriptsInfoPtr
ServiceWorkerInstalledScriptsSender::CreateInfoAndBind() { ServiceWorkerInstalledScriptsSender::CreateInfoAndBind() {
DCHECK_EQ(State::kNotStarted, state_);
std::vector<ServiceWorkerDatabase::ResourceRecord> resources;
std::vector<GURL> installed_urls;
owner_->script_cache_map()->GetResources(&resources);
for (const auto& resource : resources) {
installed_urls.emplace_back(resource.url);
if (resource.url == main_script_url_)
main_script_id_ = resource.resource_id;
else
imported_scripts_.emplace(resource.resource_id, resource.url);
}
if (installed_urls.empty())
return nullptr;
auto info = mojom::ServiceWorkerInstalledScriptsInfo::New(); auto info = mojom::ServiceWorkerInstalledScriptsInfo::New();
info->manager_request = mojo::MakeRequest(&manager_); info->manager_request = mojo::MakeRequest(&manager_);
info->installed_urls = std::move(installed_urls);
// TODO(shimazu): Read all installed urls and start pushing scripts.
return info; return info;
} }
void ServiceWorkerInstalledScriptsSender::Start() {
DCHECK_EQ(State::kNotStarted, state_);
// Return if no script has been installed.
if (main_script_id_ == kInvalidServiceWorkerResourceId) {
state_ = State::kFinished;
return;
}
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("ServiceWorker",
"ServiceWorkerInstalledScriptsSender", this,
"main_script_url", main_script_url_.spec());
state_ = State::kSendingMainScript;
StartSendingScript(main_script_id_);
}
void ServiceWorkerInstalledScriptsSender::StartSendingScript(
int64_t resource_id) {
DCHECK(!running_sender_);
DCHECK(state_ == State::kSendingMainScript ||
state_ == State::kSendingImportedScript);
auto reader = context_->storage()->CreateResponseReader(resource_id);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("ServiceWorker", "SendingScript", this,
"script_url", CurrentSendingURL().spec());
running_sender_ = base::MakeUnique<Sender>(std::move(reader), this);
running_sender_->Start();
}
void ServiceWorkerInstalledScriptsSender::SendScriptInfoToRenderer(
std::string encoding,
std::unordered_map<std::string, std::string> headers,
mojo::ScopedDataPipeConsumerHandle meta_data_handle,
mojo::ScopedDataPipeConsumerHandle body_handle) {
DCHECK(running_sender_);
DCHECK(state_ == State::kSendingMainScript ||
state_ == State::kSendingImportedScript);
TRACE_EVENT_NESTABLE_ASYNC_INSTANT0("ServiceWorker",
"SendScriptInfoToRenderer", this);
auto script_info = mojom::ServiceWorkerScriptInfo::New();
script_info->script_url = CurrentSendingURL();
script_info->headers = std::move(headers);
script_info->encoding = std::move(encoding);
script_info->body = std::move(body_handle);
script_info->meta_data = std::move(meta_data_handle);
manager_->TransferInstalledScript(std::move(script_info));
}
void ServiceWorkerInstalledScriptsSender::OnHttpInfoRead(
scoped_refptr<HttpResponseInfoIOBuffer> http_info) {
DCHECK(running_sender_);
DCHECK(state_ == State::kSendingMainScript ||
state_ == State::kSendingImportedScript);
if (state_ == State::kSendingMainScript)
owner_->SetMainScriptHttpResponseInfo(*http_info->http_info);
}
void ServiceWorkerInstalledScriptsSender::OnFinishSendingScript() {
DCHECK(running_sender_);
DCHECK(state_ == State::kSendingMainScript ||
state_ == State::kSendingImportedScript);
TRACE_EVENT_NESTABLE_ASYNC_END0("ServiceWorker", "SendingScript", this);
running_sender_.reset();
if (state_ == State::kSendingMainScript) {
// Imported scripts are served after the main script.
imported_script_iter_ = imported_scripts_.begin();
state_ = State::kSendingImportedScript;
} else {
++imported_script_iter_;
}
if (imported_script_iter_ == imported_scripts_.end()) {
// All scripts have been sent to the renderer.
// ServiceWorkerInstalledScriptsSender's work is done now.
DCHECK_EQ(State::kSendingImportedScript, state_);
state_ = State::kFinished;
TRACE_EVENT_NESTABLE_ASYNC_END0(
"ServiceWorker", "ServiceWorkerInstalledScriptsSender", this);
return;
}
// Start sending the next script.
StartSendingScript(imported_script_iter_->first);
}
void ServiceWorkerInstalledScriptsSender::OnAbortSendingScript(Status status) {
DCHECK(running_sender_);
DCHECK(state_ == State::kSendingMainScript ||
state_ == State::kSendingImportedScript);
DCHECK_NE(Status::kSuccess, status);
// TODO(shimazu): Report the error to ServiceWorkerVersion and record its
// metrics.
NOTIMPLEMENTED();
}
const GURL& ServiceWorkerInstalledScriptsSender::CurrentSendingURL() {
if (state_ == State::kSendingMainScript)
return main_script_url_;
return imported_script_iter_->second;
}
} // namespace content } // namespace content
...@@ -9,13 +9,20 @@ ...@@ -9,13 +9,20 @@
namespace content { namespace content {
struct HttpResponseInfoIOBuffer;
class ServiceWorkerContextCore;
class ServiceWorkerVersion;
// ServiceWorkerInstalledScriptsSender serves the service worker's installed // ServiceWorkerInstalledScriptsSender serves the service worker's installed
// scripts from ServiceWorkerStorage to the renderer through Mojo data pipes. // scripts from ServiceWorkerStorage to the renderer through Mojo data pipes.
// ServiceWorkerInstalledScriptsSender is owned by ServiceWorkerVersion. It is // ServiceWorkerInstalledScriptsSender is owned by ServiceWorkerVersion. It is
// created for worker startup and lives as long as the worker is running. // created for worker startup and lives as long as the worker is running.
class ServiceWorkerInstalledScriptsSender { class CONTENT_EXPORT ServiceWorkerInstalledScriptsSender {
public: public:
ServiceWorkerInstalledScriptsSender(); ServiceWorkerInstalledScriptsSender(
ServiceWorkerVersion* owner,
const GURL& main_script_url,
base::WeakPtr<ServiceWorkerContextCore> context);
~ServiceWorkerInstalledScriptsSender(); ~ServiceWorkerInstalledScriptsSender();
// Creates a Mojo struct (mojom::ServiceWorkerInstalledScriptsInfo) and sets // Creates a Mojo struct (mojom::ServiceWorkerInstalledScriptsInfo) and sets
...@@ -23,8 +30,54 @@ class ServiceWorkerInstalledScriptsSender { ...@@ -23,8 +30,54 @@ class ServiceWorkerInstalledScriptsSender {
// on the renderer. // on the renderer.
mojom::ServiceWorkerInstalledScriptsInfoPtr CreateInfoAndBind(); mojom::ServiceWorkerInstalledScriptsInfoPtr CreateInfoAndBind();
// Starts sending installed scripts to the worker.
void Start();
private: private:
class Sender;
enum class Status {
kSuccess,
kNoHttpInfoError,
kCreateDataPipeError,
kConnectionError,
kResponseReaderError,
kMetaDataSenderError,
};
enum class State {
kNotStarted,
kSendingMainScript,
kSendingImportedScript,
kFinished,
};
void StartSendingScript(int64_t resource_id);
// Called from |running_sender_|.
void SendScriptInfoToRenderer(
std::string encoding,
std::unordered_map<std::string, std::string> headers,
mojo::ScopedDataPipeConsumerHandle meta_data_handle,
mojo::ScopedDataPipeConsumerHandle body_handle);
void OnHttpInfoRead(scoped_refptr<HttpResponseInfoIOBuffer> http_info);
void OnFinishSendingScript();
void OnAbortSendingScript(Status status);
const GURL& CurrentSendingURL();
ServiceWorkerVersion* owner_;
const GURL main_script_url_;
int main_script_id_;
mojom::ServiceWorkerInstalledScriptsManagerPtr manager_; mojom::ServiceWorkerInstalledScriptsManagerPtr manager_;
std::unique_ptr<Sender> running_sender_;
State state_;
std::map<int64_t /* resource_id */, GURL> imported_scripts_;
std::map<int64_t /* resource_id */, GURL>::iterator imported_script_iter_;
base::WeakPtr<ServiceWorkerContextCore> context_;
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerInstalledScriptsSender);
}; };
} // namespace content } // namespace content
......
// Copyright 2017 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 "content/browser/service_worker/service_worker_installed_scripts_sender.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_disk_cache.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/base/io_buffer.h"
#include "net/base/test_completion_callback.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
void WriteBodyToDiskCache(
ServiceWorkerResponseWriter* writer,
const std::vector<std::pair<std::string, std::string>>& headers,
const std::string& body) {
std::unique_ptr<net::HttpResponseInfo> info =
base::MakeUnique<net::HttpResponseInfo>();
info->request_time = base::Time::Now();
info->response_time = base::Time::Now();
info->headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.0 200 OK\0\0");
for (const auto& header : headers)
info->headers->AddHeader(header.first + ": " + header.second);
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer =
base::MakeRefCounted<HttpResponseInfoIOBuffer>(info.release());
info_buffer->response_data_size = body.size();
{
net::TestCompletionCallback cb;
writer->WriteInfo(info_buffer.get(), cb.callback());
int rv = cb.WaitForResult();
EXPECT_GE(rv, 0);
}
scoped_refptr<net::IOBuffer> body_buffer =
base::MakeRefCounted<net::StringIOBuffer>(body);
{
net::TestCompletionCallback cb;
writer->WriteData(body_buffer.get(), body.size(), cb.callback());
int rv = cb.WaitForResult();
EXPECT_EQ(body.size(), static_cast<size_t>(rv));
}
}
void WriteMetaDataToDiskCache(ServiceWorkerResponseMetadataWriter* writer,
const std::string& meta_data) {
scoped_refptr<net::IOBuffer> meta_data_buffer =
base::MakeRefCounted<net::StringIOBuffer>(meta_data);
base::RunLoop loop;
writer->WriteMetadata(
meta_data_buffer.get(), meta_data.size(),
base::Bind(
[](base::Closure closure, int expected, int result) {
EXPECT_EQ(expected, result);
closure.Run();
},
loop.QuitClosure(), meta_data.size()));
loop.Run();
}
void ReadDataPipeInternal(mojo::DataPipeConsumerHandle handle,
std::string* result,
base::OnceClosure quit_closure) {
while (true) {
uint32_t num_bytes;
const void* buffer = nullptr;
MojoResult rv = mojo::BeginReadDataRaw(handle, &buffer, &num_bytes,
MOJO_READ_DATA_FLAG_NONE);
switch (rv) {
case MOJO_RESULT_BUSY:
case MOJO_RESULT_INVALID_ARGUMENT:
NOTREACHED();
return;
case MOJO_RESULT_FAILED_PRECONDITION:
std::move(quit_closure).Run();
return;
case MOJO_RESULT_SHOULD_WAIT:
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&ReadDataPipeInternal, handle, result,
base::Passed(&quit_closure)));
return;
case MOJO_RESULT_OK:
EXPECT_NE(nullptr, buffer);
EXPECT_GT(num_bytes, 0u);
uint32_t before_size = result->size();
result->append(static_cast<const char*>(buffer), num_bytes);
uint32_t read_size = result->size() - before_size;
EXPECT_EQ(num_bytes, read_size);
rv = mojo::EndReadDataRaw(handle, read_size);
EXPECT_EQ(MOJO_RESULT_OK, rv);
break;
}
}
NOTREACHED();
return;
}
std::string ReadDataPipe(mojo::ScopedDataPipeConsumerHandle handle) {
EXPECT_TRUE(handle.is_valid());
std::string result;
base::RunLoop loop;
ReadDataPipeInternal(handle.get(), &result, loop.QuitClosure());
loop.Run();
return result;
}
} // namespace
class ExpectedScriptInfo {
public:
ExpectedScriptInfo(
int64_t resource_id,
const GURL& script_url,
const std::vector<std::pair<std::string, std::string>>& headers,
const std::string& encoding,
const std::string& body,
const std::string& meta_data)
: resource_id_(resource_id),
script_url_(script_url),
headers_(headers),
encoding_(encoding),
body_(body),
meta_data_(meta_data) {}
ServiceWorkerDatabase::ResourceRecord WriteToDiskCache(
ServiceWorkerStorage* storage) const {
auto body_writer = storage->CreateResponseWriter(resource_id_);
WriteBodyToDiskCache(body_writer.get(), headers_, body_);
auto metadata_writer = storage->CreateResponseMetadataWriter(resource_id_);
WriteMetaDataToDiskCache(metadata_writer.get(), meta_data_);
return ServiceWorkerDatabase::ResourceRecord(resource_id_, script_url_,
body_.size());
}
void CheckIfIdentical(
const mojom::ServiceWorkerScriptInfoPtr& script_info) const {
EXPECT_EQ(script_url_, script_info->script_url);
EXPECT_EQ(encoding_, script_info->encoding);
for (const auto& header : headers_) {
EXPECT_TRUE(base::ContainsKey(script_info->headers, header.first));
EXPECT_EQ(header.second, script_info->headers[header.first]);
script_info->headers.erase(header.first);
}
EXPECT_EQ(0u, script_info->headers.size());
EXPECT_TRUE(script_info->body.is_valid());
EXPECT_TRUE(script_info->meta_data.is_valid());
std::string body = ReadDataPipe(std::move(script_info->body));
EXPECT_EQ(body_, body);
std::string meta_data = ReadDataPipe(std::move(script_info->meta_data));
EXPECT_EQ(meta_data_, meta_data);
}
const GURL& script_url() const { return script_url_; }
private:
const int64_t resource_id_;
const GURL script_url_;
const std::vector<std::pair<std::string, std::string>> headers_;
const std::string encoding_;
const std::string body_;
const std::string meta_data_;
};
class MockServiceWorkerInstalledScriptsManager
: public mojom::ServiceWorkerInstalledScriptsManager {
public:
MockServiceWorkerInstalledScriptsManager(
std::vector<GURL> installed_urls,
mojom::ServiceWorkerInstalledScriptsManagerRequest request)
: binding_(this, std::move(request)),
installed_urls_(std::move(installed_urls)),
next_url_(installed_urls_.begin()) {}
mojom::ServiceWorkerScriptInfoPtr WaitUntilTransferInstalledScript() {
EXPECT_TRUE(incoming_script_info_.is_null());
EXPECT_FALSE(transfer_installed_script_waiter_);
base::RunLoop loop;
transfer_installed_script_waiter_ = loop.QuitClosure();
loop.Run();
EXPECT_FALSE(incoming_script_info_.is_null());
return std::move(incoming_script_info_);
}
void TransferInstalledScript(
mojom::ServiceWorkerScriptInfoPtr script_info) override {
EXPECT_TRUE(incoming_script_info_.is_null());
EXPECT_TRUE(transfer_installed_script_waiter_);
EXPECT_EQ(*next_url_, script_info->script_url);
incoming_script_info_ = std::move(script_info);
if (transfer_installed_script_waiter_)
std::move(transfer_installed_script_waiter_).Run();
++next_url_;
}
private:
mojo::Binding<mojom::ServiceWorkerInstalledScriptsManager> binding_;
// This is in the order to be served.
const std::vector<GURL> installed_urls_;
std::vector<GURL>::const_iterator next_url_;
base::OnceClosure transfer_installed_script_waiter_;
mojom::ServiceWorkerScriptInfoPtr incoming_script_info_;
DISALLOW_COPY_AND_ASSIGN(MockServiceWorkerInstalledScriptsManager);
};
class ServiceWorkerInstalledScriptsSenderTest : public testing::Test {
public:
ServiceWorkerInstalledScriptsSenderTest()
: thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {}
protected:
void SetUp() override {
helper_ = base::MakeUnique<EmbeddedWorkerTestHelper>(base::FilePath());
context()->storage()->LazyInitialize(base::Bind(&base::DoNothing));
base::RunLoop().RunUntilIdle();
pattern_ = GURL("http://www.example.com/test/");
registration_ = base::MakeRefCounted<ServiceWorkerRegistration>(
ServiceWorkerRegistrationOptions(pattern_), 1L, context()->AsWeakPtr());
version_ = base::MakeRefCounted<ServiceWorkerVersion>(
registration_.get(),
GURL("http://www.example.com/test/service_worker.js"),
context()->storage()->NewVersionId(), context()->AsWeakPtr());
}
void TearDown() override {
version_ = nullptr;
registration_ = nullptr;
helper_.reset();
}
ServiceWorkerContextCore* context() { return helper_->context(); }
ServiceWorkerVersion* version() { return version_.get(); }
private:
TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
GURL pattern_;
scoped_refptr<ServiceWorkerRegistration> registration_;
scoped_refptr<ServiceWorkerVersion> version_;
};
TEST_F(ServiceWorkerInstalledScriptsSenderTest, SendScripts) {
const GURL kMainScriptURL = version()->script_url();
std::map<GURL, ExpectedScriptInfo> kExpectedScriptInfoMap = {
{kMainScriptURL,
{1,
kMainScriptURL,
{{"Content-Length", "19"},
{"Content-Type", "text/javascript; charset=utf-8"},
{"TestHeader", "BlahBlah"}},
"utf-8",
"I'm the script body!",
"I'm the meta data!"}},
{GURL("https://example.com/imported1"),
{2,
GURL("https://example.com/imported1"),
{{"Content-Length", "22"},
{"Content-Type", "text/javascript; charset=euc-jp"},
{"TestHeader", "BlahBlah"}},
"euc-jp",
"I'm imported script 1!",
"I'm the meta data for imported script 1!"}},
{GURL("https://example.com/imported2"),
{3,
GURL("https://example.com/imported2"),
{{"Content-Length", "22"},
{"Content-Type", "text/javascript; charset=shift_jis"},
{"TestHeader", "BlahBlah"}},
"shift_jis",
"I'm imported script 2!",
"I'm the meta data for imported script 2!"}},
};
{
std::vector<ServiceWorkerDatabase::ResourceRecord> records;
for (const auto& info : kExpectedScriptInfoMap)
records.push_back(info.second.WriteToDiskCache(context()->storage()));
version()->script_cache_map()->SetResources(records);
}
auto sender = base::MakeUnique<ServiceWorkerInstalledScriptsSender>(
version(), kMainScriptURL, context()->AsWeakPtr());
std::unique_ptr<MockServiceWorkerInstalledScriptsManager> renderer_manager;
{
mojom::ServiceWorkerInstalledScriptsInfoPtr scripts_info =
sender->CreateInfoAndBind();
ASSERT_TRUE(scripts_info);
ASSERT_EQ(kExpectedScriptInfoMap.size(),
scripts_info->installed_urls.size());
for (const auto& url : scripts_info->installed_urls)
EXPECT_TRUE(base::ContainsKey(kExpectedScriptInfoMap, url));
EXPECT_TRUE(scripts_info->manager_request.is_pending());
renderer_manager =
base::MakeUnique<MockServiceWorkerInstalledScriptsManager>(
std::move(scripts_info->installed_urls),
std::move(scripts_info->manager_request));
}
ASSERT_TRUE(renderer_manager);
sender->Start();
while (kExpectedScriptInfoMap.size() > 0) {
auto script_info = renderer_manager->WaitUntilTransferInstalledScript();
EXPECT_TRUE(
base::ContainsKey(kExpectedScriptInfoMap, script_info->script_url));
const auto& info = kExpectedScriptInfoMap.at(script_info->script_url);
info.CheckIfIdentical(script_info);
kExpectedScriptInfoMap.erase(script_info->script_url);
}
}
} // namespace content
...@@ -260,6 +260,7 @@ class CONTENT_EXPORT ServiceWorkerStorage ...@@ -260,6 +260,7 @@ class CONTENT_EXPORT ServiceWorkerStorage
friend class ServiceWorkerContextRequestHandlerTest; friend class ServiceWorkerContextRequestHandlerTest;
friend class ServiceWorkerReadFromCacheJobTest; friend class ServiceWorkerReadFromCacheJobTest;
friend class ServiceWorkerRequestHandlerTest; friend class ServiceWorkerRequestHandlerTest;
friend class ServiceWorkerInstalledScriptsSenderTest;
friend class ServiceWorkerURLRequestJobTest; friend class ServiceWorkerURLRequestJobTest;
friend class ServiceWorkerVersionBrowserTest; friend class ServiceWorkerVersionBrowserTest;
friend class ServiceWorkerVersionTest; friend class ServiceWorkerVersionTest;
......
...@@ -1476,11 +1476,14 @@ void ServiceWorkerVersion::StartWorkerInternal() { ...@@ -1476,11 +1476,14 @@ void ServiceWorkerVersion::StartWorkerInternal() {
params->pause_after_download = pause_after_download_; params->pause_after_download = pause_after_download_;
mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info; mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info;
if (ServiceWorkerUtils::IsScriptStreamingEnabled()) { if (ServiceWorkerUtils::IsScriptStreamingEnabled() &&
!pause_after_download_) {
DCHECK(!installed_scripts_sender_); DCHECK(!installed_scripts_sender_);
installed_scripts_sender_ = installed_scripts_sender_ =
base::MakeUnique<ServiceWorkerInstalledScriptsSender>(); base::MakeUnique<ServiceWorkerInstalledScriptsSender>(
this, script_url(), context());
installed_scripts_info = installed_scripts_sender_->CreateInfoAndBind(); installed_scripts_info = installed_scripts_sender_->CreateInfoAndBind();
installed_scripts_sender_->Start();
} }
embedded_worker_->Start( embedded_worker_->Start(
......
...@@ -22,8 +22,9 @@ struct ServiceWorkerScriptInfo { ...@@ -22,8 +22,9 @@ struct ServiceWorkerScriptInfo {
map<string, string> headers; map<string, string> headers;
// A handle for receiving the script body. // A handle for receiving the script body.
handle<data_pipe_consumer> body; handle<data_pipe_consumer> body;
// A handle for receiving the V8 code cached metadata. // A handle for receiving the V8 code cached metadata. This is null when no
handle<data_pipe_consumer> meta_data; // meta data is available.
handle<data_pipe_consumer>? meta_data;
}; };
// Renderer-side interface. The browser uses this interface to send // Renderer-side interface. The browser uses this interface to send
......
...@@ -120,8 +120,11 @@ EmbeddedWorkerInstanceClientImpl::StartWorkerContext( ...@@ -120,8 +120,11 @@ EmbeddedWorkerInstanceClientImpl::StartWorkerContext(
mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info, mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info,
std::unique_ptr<ServiceWorkerContextClient> context_client) { std::unique_ptr<ServiceWorkerContextClient> context_client) {
std::unique_ptr<blink::WebServiceWorkerInstalledScriptsManager> manager; std::unique_ptr<blink::WebServiceWorkerInstalledScriptsManager> manager;
if (ServiceWorkerUtils::IsScriptStreamingEnabled()) { // |installed_scripts_info| is null if scripts should be served by net layer,
DCHECK(installed_scripts_info); // when the worker is not installed, or the worker is launched for checking
// the update.
if (ServiceWorkerUtils::IsScriptStreamingEnabled() &&
installed_scripts_info) {
manager = WebServiceWorkerInstalledScriptsManagerImpl::Create( manager = WebServiceWorkerInstalledScriptsManagerImpl::Create(
std::move(installed_scripts_info), io_thread_runner_); std::move(installed_scripts_info), io_thread_runner_);
} }
......
...@@ -27,6 +27,10 @@ class Receiver { ...@@ -27,6 +27,10 @@ class Receiver {
watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL) {} watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL) {}
void Start(base::OnceClosure callback) { void Start(base::OnceClosure callback) {
if (!handle_.is_valid()) {
std::move(callback).Run();
return;
}
callback_ = std::move(callback); callback_ = std::move(callback);
// base::Unretained is safe because |watcher_| is owned by |this|. // base::Unretained is safe because |watcher_| is owned by |this|.
watcher_.Watch(handle_.get(), MOJO_HANDLE_SIGNAL_READABLE, watcher_.Watch(handle_.get(), MOJO_HANDLE_SIGNAL_READABLE,
......
...@@ -1315,6 +1315,7 @@ test("content_unittests") { ...@@ -1315,6 +1315,7 @@ test("content_unittests") {
"../browser/service_worker/service_worker_database_unittest.cc", "../browser/service_worker/service_worker_database_unittest.cc",
"../browser/service_worker/service_worker_dispatcher_host_unittest.cc", "../browser/service_worker/service_worker_dispatcher_host_unittest.cc",
"../browser/service_worker/service_worker_handle_unittest.cc", "../browser/service_worker/service_worker_handle_unittest.cc",
"../browser/service_worker/service_worker_installed_scripts_sender_unittest.cc",
"../browser/service_worker/service_worker_job_unittest.cc", "../browser/service_worker/service_worker_job_unittest.cc",
"../browser/service_worker/service_worker_lifetime_tracker_unittest.cc", "../browser/service_worker/service_worker_lifetime_tracker_unittest.cc",
"../browser/service_worker/service_worker_metrics_unittest.cc", "../browser/service_worker/service_worker_metrics_unittest.cc",
......
...@@ -114,8 +114,8 @@ WebEmbeddedWorkerImpl::WebEmbeddedWorkerImpl( ...@@ -114,8 +114,8 @@ WebEmbeddedWorkerImpl::WebEmbeddedWorkerImpl(
waiting_for_debugger_state_(kNotWaitingForDebugger) { waiting_for_debugger_state_(kNotWaitingForDebugger) {
RunningWorkerInstances().insert(this); RunningWorkerInstances().insert(this);
if (RuntimeEnabledFeatures::ServiceWorkerScriptStreamingEnabled()) { if (RuntimeEnabledFeatures::ServiceWorkerScriptStreamingEnabled() &&
DCHECK(installed_scripts_manager); installed_scripts_manager) {
installed_scripts_manager_ = installed_scripts_manager_ =
WTF::MakeUnique<ServiceWorkerInstalledScriptsManager>( WTF::MakeUnique<ServiceWorkerInstalledScriptsManager>(
std::move(installed_scripts_manager)); std::move(installed_scripts_manager));
......
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