blob: c04592facc429e46d6eb052de4e926806b9f928e [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/web_bundle/web_bundle_manager.h"
#include <algorithm>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "components/web_package/web_bundle_utils.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/network_context.h"
#include "services/network/public/mojom/web_bundle_handle.mojom.h"
#include "services/network/web_bundle/web_bundle_memory_quota_consumer.h"
#include "services/network/web_bundle/web_bundle_url_loader_factory.h"
namespace network {
class WebBundleManager::MemoryQuotaConsumer
: public WebBundleMemoryQuotaConsumer {
public:
MemoryQuotaConsumer(base::WeakPtr<WebBundleManager> manager,
int32_t process_id)
: manager_(std::move(manager)), process_id_(process_id) {}
MemoryQuotaConsumer(const MemoryQuotaConsumer&) = delete;
MemoryQuotaConsumer& operator=(const MemoryQuotaConsumer&) = delete;
~MemoryQuotaConsumer() override {
if (!manager_)
return;
manager_->ReleaseMemoryForProcess(process_id_, allocated_bytes_);
}
bool AllocateMemory(uint64_t num_bytes) override {
if (!manager_)
return false;
if (!manager_->AllocateMemoryForProcess(process_id_, num_bytes))
return false;
allocated_bytes_ += num_bytes;
return true;
}
private:
base::WeakPtr<WebBundleManager> manager_;
const int32_t process_id_;
uint64_t allocated_bytes_ = 0;
};
WebBundleManager::WebBundleManager()
: max_memory_per_process_(web_package::kDefaultMaxMemoryPerProcess) {}
WebBundleManager::~WebBundleManager() = default;
base::WeakPtr<WebBundleURLLoaderFactory>
WebBundleManager::CreateWebBundleURLLoaderFactory(
const GURL& bundle_url,
const ResourceRequest::WebBundleTokenParams& web_bundle_token_params,
int32_t process_id,
const CrossOriginEmbedderPolicy& cross_origin_embedder_policy,
mojom::CrossOriginEmbedderPolicyReporter* coep_reporter) {
Key key = GetKey(web_bundle_token_params, process_id);
DCHECK(factories_.find(key) == factories_.end());
DCHECK(web_bundle_token_params.handle.is_valid());
DCHECK_NE(process_id, mojom::kBrowserProcessId);
mojo::Remote<mojom::WebBundleHandle> remote(
web_bundle_token_params.CloneHandle());
// Set a disconnect handler to remove a WebBundleURLLoaderFactory from this
// WebBundleManager when the corresponding endpoint in the renderer is
// removed.
remote.set_disconnect_handler(
base::BindOnce(&WebBundleManager::DisconnectHandler,
// |this| outlives |remote|.
base::Unretained(this), key));
auto factory = std::make_unique<WebBundleURLLoaderFactory>(
bundle_url, web_bundle_token_params, std::move(remote),
std::make_unique<MemoryQuotaConsumer>(weak_ptr_factory_.GetWeakPtr(),
process_id),
cross_origin_embedder_policy, coep_reporter);
// Process pending subresource loaders if there are.
// These subresource requests arrived earlier than the request for the bundle.
auto it = pending_loaders_.find(key);
if (it != pending_loaders_.end()) {
for (auto& loader : it->second)
factory->StartLoader(loader);
pending_loaders_.erase(it);
}
auto weak_factory = factory->GetWeakPtr();
factories_.insert({key, std::move(factory)});
return weak_factory;
}
WebBundleManager::Key WebBundleManager::GetKey(
const ResourceRequest::WebBundleTokenParams& token_params,
int32_t process_id) {
// If the request is from the browser process, use
// WebBundleTokenParams::render_process_id for matching.
if (process_id == mojom::kBrowserProcessId)
process_id = token_params.render_process_id;
return {process_id, token_params.token};
}
base::WeakPtr<WebBundleURLLoaderFactory>
WebBundleManager::GetWebBundleURLLoaderFactory(const Key& key) {
auto it = factories_.find(key);
if (it == factories_.end()) {
return nullptr;
}
return it->second->GetWeakPtr();
}
void WebBundleManager::StartSubresourceRequest(
mojo::PendingReceiver<mojom::URLLoader> receiver,
const ResourceRequest& url_request,
mojo::PendingRemote<mojom::URLLoaderClient> client,
int32_t process_id,
mojo::Remote<mojom::TrustedHeaderClient> trusted_header_client) {
DCHECK(url_request.web_bundle_token_params.has_value());
DCHECK(!url_request.web_bundle_token_params->handle.is_valid());
Key key = GetKey(*url_request.web_bundle_token_params, process_id);
base::WeakPtr<WebBundleURLLoaderFactory> web_bundle_url_loader_factory =
GetWebBundleURLLoaderFactory(key);
base::Time request_start_time = base::Time::Now();
base::TimeTicks request_start_time_ticks = base::TimeTicks::Now();
if (web_bundle_url_loader_factory) {
auto loader = WebBundleURLLoaderFactory::CreateURLLoader(
std::move(receiver), url_request, std::move(client),
std::move(trusted_header_client), request_start_time,
request_start_time_ticks, base::DoNothing());
web_bundle_url_loader_factory->StartLoader(loader);
return;
}
// A request for subresource arrives earlier than a request for a webbundle.
pending_loaders_[key].push_back(WebBundleURLLoaderFactory::CreateURLLoader(
std::move(receiver), url_request, std::move(client),
std::move(trusted_header_client), request_start_time,
request_start_time_ticks,
base::BindOnce(&WebBundleManager::CleanUpWillBeDeletedURLLoader,
weak_ptr_factory_.GetWeakPtr(), key)));
}
void WebBundleManager::CleanUpWillBeDeletedURLLoader(
Key key,
WebBundleURLLoaderFactory::URLLoader* will_be_deleted_url_loader) {
auto it = pending_loaders_.find(key);
if (it == pending_loaders_.end())
return;
// Since we use std::vector for holding pending loaders, the clean up may take
// O(N^2) if CleanUpWillBeDeletedURLLoader is called repeatedly. We might want
// to use more appropriate data structure if this becomes a performance
// issue. As of now, this happens only in non-regular cases; the bundle is
// blocked by, for example, Chrome Extensions APIs.
auto to_remove = std::ranges::remove_if(
it->second, [will_be_deleted_url_loader](auto pending_loader) {
return !pending_loader ||
pending_loader.get() == will_be_deleted_url_loader;
});
it->second.erase(to_remove.begin(), to_remove.end());
if (it->second.empty()) {
pending_loaders_.erase(key);
}
}
void WebBundleManager::DisconnectHandler(Key key) {
factories_.erase(key);
DCHECK(!base::Contains(pending_loaders_, key));
}
bool WebBundleManager::AllocateMemoryForProcess(int32_t process_id,
uint64_t num_bytes) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (memory_usage_per_process_[process_id] + num_bytes >
max_memory_per_process_) {
return false;
}
memory_usage_per_process_[process_id] += num_bytes;
if (max_memory_usage_per_process_[process_id] <
memory_usage_per_process_[process_id]) {
max_memory_usage_per_process_[process_id] =
memory_usage_per_process_[process_id];
}
return true;
}
void WebBundleManager::ReleaseMemoryForProcess(int32_t process_id,
uint64_t num_bytes) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GE(memory_usage_per_process_[process_id], num_bytes);
memory_usage_per_process_[process_id] -= num_bytes;
if (memory_usage_per_process_[process_id] == 0) {
memory_usage_per_process_.erase(process_id);
base::UmaHistogramCustomCounts(
"SubresourceWebBundles.MaxMemoryUsagePerProcess",
max_memory_usage_per_process_[process_id], 1, 50000000, 50);
max_memory_usage_per_process_.erase(process_id);
}
}
} // namespace network