blob: 40ab612c1f82aa1b03e75c416c485784fbe9dcb5 [file] [log] [blame]
// 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/platform/loader/fetch/url_loader/worker_main_script_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/loader/referrer_utils.h"
#include "third_party/blink/public/mojom/loader/code_cache.mojom-shared.h"
#include "third_party/blink/public/platform/resource_load_info_notifier_wrapper.h"
#include "third_party/blink/public/platform/url_conversion.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_loader.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "third_party/blink/renderer/platform/loader/cors/cors.h"
#include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.h"
#include "third_party/blink/renderer/platform/loader/fetch/cached_metadata_handler.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_context.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_load_info.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_load_timing.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h"
#include "third_party/blink/renderer/platform/loader/fetch/script_cached_metadata_handler.h"
#include "third_party/blink/renderer/platform/loader/fetch/url_loader/worker_main_script_loader_client.h"
namespace blink {
WorkerMainScriptLoader::WorkerMainScriptLoader() = default;
WorkerMainScriptLoader::~WorkerMainScriptLoader() = default;
void WorkerMainScriptLoader::Start(
const FetchParameters& fetch_params,
std::unique_ptr<WorkerMainScriptLoadParameters>
worker_main_script_load_params,
FetchContext* fetch_context,
ResourceLoadObserver* resource_load_observer,
WorkerMainScriptLoaderClient* client) {
DCHECK(resource_load_observer);
DCHECK(client);
initial_request_ = fetch_params.GetResourceRequest();
resource_loader_options_ = fetch_params.Options();
initial_request_url_ = fetch_params.GetResourceRequest().Url();
last_request_url_ = initial_request_url_;
resource_load_observer_ = resource_load_observer;
fetch_context_ = fetch_context;
client_ = client;
resource_load_info_notifier_wrapper_ =
fetch_context->CreateResourceLoadInfoNotifierWrapper();
// TODO(crbug.com/929370): Support CSP check to post violation reports for
// worker top-level scripts, if off-the-main-thread fetch is enabled.
ResourceRequest resource_request(initial_request_);
resource_load_observer_->WillSendRequest(
initial_request_.InspectorId(), resource_request,
/*redirect_response=*/ResourceResponse(), ResourceType::kScript,
resource_loader_options_.initiator_info,
RenderBlockingBehavior::kNonBlocking);
resource_load_info_notifier_wrapper_->NotifyResourceLoadInitiated(
/*request_id=*/-1, initial_request_url_,
initial_request_.HttpMethod().Latin1(),
WebStringToGURL(WebString(initial_request_.ReferrerString())),
initial_request_.GetRequestDestination(), net::HIGHEST);
if (!worker_main_script_load_params->redirect_responses.empty()) {
HandleRedirections(worker_main_script_load_params->redirect_infos,
worker_main_script_load_params->redirect_responses);
}
WebURLResponse response;
auto response_head = std::move(worker_main_script_load_params->response_head);
WebURLLoader::PopulateURLResponse(
WebURL(last_request_url_), *response_head, &response,
response_head->ssl_info.has_value(), /*request_id=*/-1);
resource_response_ = response.ToResourceResponse();
resource_load_info_notifier_wrapper_->NotifyResourceResponseReceived(
std::move(response_head), PreviewsTypes::kPreviewsUnspecified);
resource_load_observer_->DidReceiveResponse(
initial_request_.InspectorId(), resource_request, resource_response_,
/*resource=*/nullptr,
ResourceLoadObserver::ResponseSource::kNotFromMemoryCache);
if (resource_response_.IsHTTP() &&
!cors::IsOkStatus(resource_response_.HttpStatusCode())) {
client_->OnFailedLoadingWorkerMainScript();
resource_load_observer_->DidFailLoading(
initial_request_.Url(), initial_request_.InspectorId(),
ResourceError(net::ERR_FAILED, last_request_url_, base::nullopt),
resource_response_.EncodedDataLength(),
ResourceLoadObserver::IsInternalRequest(
resource_loader_options_.initiator_info.name ==
fetch_initiator_type_names::kInternal));
return;
}
script_encoding_ =
resource_response_.TextEncodingName().IsEmpty()
? UTF8Encoding()
: WTF::TextEncoding(resource_response_.TextEncodingName());
url_loader_remote_.Bind(std::move(
worker_main_script_load_params->url_loader_client_endpoints->url_loader));
receiver_.Bind(
std::move(worker_main_script_load_params->url_loader_client_endpoints
->url_loader_client));
receiver_.set_disconnect_handler(base::BindOnce(
&WorkerMainScriptLoader::OnConnectionClosed, base::Unretained(this)));
data_pipe_ = std::move(worker_main_script_load_params->response_body);
client_->OnStartLoadingBody(resource_response_);
StartLoadingBody();
}
void WorkerMainScriptLoader::Cancel() {
if (has_cancelled_)
return;
has_cancelled_ = true;
if (watcher_ && watcher_->IsWatching())
watcher_->Cancel();
receiver_.reset();
url_loader_remote_.reset();
}
void WorkerMainScriptLoader::OnReceiveResponse(
network::mojom::URLResponseHeadPtr response_head) {
// This has already happened in the browser process.
NOTREACHED();
}
void WorkerMainScriptLoader::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr response_head) {
// This has already happened in the browser process.
NOTREACHED();
}
void WorkerMainScriptLoader::OnUploadProgress(
int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) {
// This has already happened in the browser process.
NOTREACHED();
}
void WorkerMainScriptLoader::OnReceiveCachedMetadata(
mojo_base::BigBuffer data) {}
void WorkerMainScriptLoader::OnTransferSizeUpdated(int32_t transfer_size_diff) {
}
void WorkerMainScriptLoader::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle handle) {
// This has already happened in the browser process.
NOTREACHED();
}
void WorkerMainScriptLoader::OnComplete(
const network::URLLoaderCompletionStatus& status) {
if (status.error_code != net::OK)
has_seen_end_of_data_ = true;
// Reports resource timing info for the worker main script.
scoped_refptr<ResourceTimingInfo> timing_info =
ResourceTimingInfo::Create(g_empty_atom, base::TimeTicks::Now(),
initial_request_.GetRequestContext(),
initial_request_.GetRequestDestination());
const int64_t encoded_data_length = resource_response_.EncodedDataLength();
timing_info->SetInitialURL(initial_request_url_);
timing_info->SetFinalResponse(resource_response_);
timing_info->SetLoadResponseEnd(status.completion_time);
timing_info->AddFinalTransferSize(
encoded_data_length == -1 ? 0 : encoded_data_length);
fetch_context_->AddResourceTiming(*timing_info);
has_received_completion_ = true;
status_ = status;
NotifyCompletionIfAppropriate();
}
SingleCachedMetadataHandler*
WorkerMainScriptLoader::CreateCachedMetadataHandler() {
// Currently we support the metadata caching only for HTTP family.
if (!initial_request_url_.ProtocolIsInHTTPFamily() ||
!resource_response_.CurrentRequestUrl().ProtocolIsInHTTPFamily()) {
return nullptr;
}
std::unique_ptr<CachedMetadataSender> cached_metadata_sender =
CachedMetadataSender::Create(
resource_response_, blink::mojom::CodeCacheType::kJavascript,
SecurityOrigin::Create(initial_request_url_));
return MakeGarbageCollected<ScriptCachedMetadataHandler>(
script_encoding_, std::move(cached_metadata_sender));
}
void WorkerMainScriptLoader::Trace(Visitor* visitor) const {
visitor->Trace(fetch_context_);
visitor->Trace(resource_load_observer_);
visitor->Trace(client_);
}
void WorkerMainScriptLoader::StartLoadingBody() {
// Loading body may be cancelled before starting by calling |Cancel()|.
if (has_cancelled_)
return;
watcher_ = std::make_unique<mojo::SimpleWatcher>(
FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL);
MojoResult rv =
watcher_->Watch(data_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE,
base::BindRepeating(&WorkerMainScriptLoader::OnReadable,
base::Unretained(this)));
DCHECK_EQ(MOJO_RESULT_OK, rv);
watcher_->ArmOrNotify();
}
void WorkerMainScriptLoader::OnReadable(MojoResult) {
// It isn't necessary to handle MojoResult here since BeginReadDataRaw()
// returns an equivalent error.
const char* buffer = nullptr;
uint32_t bytes_read = 0;
MojoResult rv =
data_pipe_->BeginReadData(reinterpret_cast<const void**>(&buffer),
&bytes_read, MOJO_READ_DATA_FLAG_NONE);
switch (rv) {
case MOJO_RESULT_BUSY:
case MOJO_RESULT_INVALID_ARGUMENT:
NOTREACHED();
return;
case MOJO_RESULT_FAILED_PRECONDITION:
has_seen_end_of_data_ = true;
NotifyCompletionIfAppropriate();
return;
case MOJO_RESULT_SHOULD_WAIT:
watcher_->ArmOrNotify();
return;
case MOJO_RESULT_OK:
break;
default:
OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
return;
}
if (bytes_read > 0) {
base::span<const char> span = base::make_span(buffer, bytes_read);
client_->DidReceiveData(span);
resource_load_observer_->DidReceiveData(initial_request_.InspectorId(),
span);
}
rv = data_pipe_->EndReadData(bytes_read);
DCHECK_EQ(rv, MOJO_RESULT_OK);
watcher_->ArmOrNotify();
}
void WorkerMainScriptLoader::NotifyCompletionIfAppropriate() {
if (!has_received_completion_ || !has_seen_end_of_data_)
return;
data_pipe_.reset();
watcher_->Cancel();
resource_load_info_notifier_wrapper_->NotifyResourceLoadCompleted(status_);
if (!client_)
return;
WorkerMainScriptLoaderClient* client = client_.Get();
client_.Clear();
if (status_.error_code == net::OK) {
client->OnFinishedLoadingWorkerMainScript();
resource_load_observer_->DidFinishLoading(
initial_request_.InspectorId(), base::TimeTicks::Now(),
resource_response_.EncodedDataLength(),
resource_response_.DecodedBodyLength(),
/*should_report_corb_blocking=*/false);
} else {
client->OnFailedLoadingWorkerMainScript();
resource_load_observer_->DidFailLoading(
last_request_url_, initial_request_.InspectorId(),
ResourceError(status_.error_code, last_request_url_, base::nullopt),
resource_response_.EncodedDataLength(),
ResourceLoadObserver::IsInternalRequest(
ResourceLoadObserver::IsInternalRequest(
resource_loader_options_.initiator_info.name ==
fetch_initiator_type_names::kInternal)));
}
}
void WorkerMainScriptLoader::OnConnectionClosed() {
if (!has_received_completion_) {
OnComplete(network::URLLoaderCompletionStatus(net::ERR_ABORTED));
return;
}
}
void WorkerMainScriptLoader::HandleRedirections(
std::vector<net::RedirectInfo>& redirect_infos,
std::vector<network::mojom::URLResponseHeadPtr>& redirect_responses) {
DCHECK_EQ(redirect_infos.size(), redirect_responses.size());
for (size_t i = 0; i < redirect_infos.size(); ++i) {
auto& redirect_info = redirect_infos[i];
auto& redirect_response = redirect_responses[i];
last_request_url_ = KURL(redirect_info.new_url);
std::unique_ptr<ResourceRequest> new_request =
initial_request_.CreateRedirectRequest(
KURL(redirect_info.new_url),
AtomicString::FromUTF8(redirect_info.new_method.data(),
redirect_info.new_method.length()),
redirect_info.new_site_for_cookies,
AtomicString::FromUTF8(redirect_info.new_referrer.data(),
redirect_info.new_referrer.length()),
ReferrerUtils::NetToMojoReferrerPolicy(
redirect_info.new_referrer_policy),
/*skip_service_worker=*/false);
WebURLResponse response;
WebURLLoader::PopulateURLResponse(
WebURL(last_request_url_), *redirect_response, &response,
redirect_response->ssl_info.has_value(), /*request_id=*/-1);
resource_load_observer_->WillSendRequest(
new_request->InspectorId(), *new_request, response.ToResourceResponse(),
ResourceType::kScript, resource_loader_options_.initiator_info,
RenderBlockingBehavior::kNonBlocking);
resource_load_info_notifier_wrapper_->NotifyResourceRedirectReceived(
redirect_info, std::move(redirect_response));
}
}
} // namespace blink