#include "content/browser/loader/resource_cache_manager.h"
#include "base/containers/contains.h"
#include "base/ranges/algorithm.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/web_contents_observer.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/mojom/loader/resource_cache.mojom.h"
namespace content {
namespace {
ResourceCacheKey CalculateResourceCacheKeyFromCommittedFrame(
RenderFrameHostImpl& render_frame_host) {
CHECK(!render_frame_host.HasPendingCommitForCrossDocumentNavigation());
return ResourceCacheKey(render_frame_host.GetProcess()->GetProcessLock(),
render_frame_host.GetLastCommittedOrigin());
}
RenderFrameHostImpl* GetEligibleRenderFrameHostFromID(
GlobalRenderFrameHostId render_frame_host_id,
bool check_pending_navigation = true) {
RenderFrameHostImpl* render_frame_host =
RenderFrameHostImpl::FromID(render_frame_host_id);
if (!render_frame_host || !render_frame_host->IsActive() ||
!render_frame_host->IsRenderFrameLive() ||
render_frame_host->is_render_frame_deleted()) {
return nullptr;
}
if (check_pending_navigation &&
render_frame_host->HasPendingCommitForCrossDocumentNavigation()) {
return nullptr;
}
return render_frame_host;
}
void PassResourceCacheRemote(RenderFrameHostImpl& hosting_render_frame_host,
RenderFrameHostImpl& target_render_frame_host) {
CHECK_NE(hosting_render_frame_host.GetGlobalId(),
target_render_frame_host.GetGlobalId());
mojo::PendingRemote<blink::mojom::ResourceCache> pending_remote;
hosting_render_frame_host.GetRemoteInterfaces()->GetInterface(
pending_remote.InitWithNewPipeAndPassReceiver());
target_render_frame_host.SetResourceCacheRemote(std::move(pending_remote));
}
}
class ResourceCacheHostObserver : public WebContentsObserver {
public:
ResourceCacheHostObserver(ResourceCacheManager* manager,
WebContentsImpl* web_contents)
: WebContentsObserver(web_contents), manager(manager) {
CHECK(manager);
}
void RenderFrameHostStateChanged(
RenderFrameHost* render_frame_host,
RenderFrameHost::LifecycleState old_state,
RenderFrameHost::LifecycleState new_state) override {
manager->RenderFrameHostStateChanged(
*static_cast<RenderFrameHostImpl*>(render_frame_host), old_state,
new_state);
}
void WebContentsDestroyed() override {
manager->DestroyHostObserver(this);
}
private:
const raw_ptr<ResourceCacheManager> manager;
};
ResourceCacheKey::ResourceCacheKey(const ProcessLock& process_lock,
const url::Origin& origin)
: process_lock(process_lock), origin(origin) {}
bool ResourceCacheKey::operator==(const ResourceCacheKey& rhs) const {
return this->process_lock == rhs.process_lock && this->origin == rhs.origin;
}
bool ResourceCacheKey::operator<(const ResourceCacheKey& rhs) const {
return this->process_lock < rhs.process_lock || this->origin < rhs.origin;
}
ResourceCacheManager::ResourceCacheEntry::ResourceCacheEntry(
mojo::Remote<blink::mojom::ResourceCache> remote,
GlobalRenderFrameHostId render_frame_host_id)
: remote(std::move(remote)), render_frame_host_id(render_frame_host_id) {}
ResourceCacheManager::ResourceCacheEntry::~ResourceCacheEntry() = default;
ResourceCacheManager::ResourceCacheManager() = default;
ResourceCacheManager::~ResourceCacheManager() = default;
void ResourceCacheManager::MaybeInitializeResourceCacheRemoteOnCommitNavigation(
mojo::PendingRemote<blink::mojom::ResourceCache>& pending_remote,
NavigationRequest& navigation_request) {
CHECK_EQ(navigation_request.state(),
NavigationRequest::NavigationState::READY_TO_COMMIT);
CHECK(!pending_remote.is_valid());
if (!navigation_request.GetURL().SchemeIsHTTPOrHTTPS()) {
return;
}
RenderFrameHostImpl* render_frame_host =
navigation_request.GetRenderFrameHost();
CHECK(render_frame_host);
const ResourceCacheKey key(render_frame_host->GetProcess()->GetProcessLock(),
*navigation_request.GetOriginToCommit());
RenderFrameHostImpl* hosting_render_frame_host =
FindOrMaybeCreateResourceCache(key);
if (hosting_render_frame_host) {
hosting_render_frame_host->GetRemoteInterfaces()->GetInterface(
pending_remote.InitWithNewPipeAndPassReceiver());
if (hosting_render_frame_host->GetGlobalId() ==
render_frame_host->GetGlobalId()) {
return;
}
}
InsertNonHostingRenderFrameId(key, render_frame_host->GetGlobalId());
}
void ResourceCacheManager::RenderFrameHostBecameIneligible(
RenderFrameHostImpl& render_frame_host) {
CHECK(!GetEligibleRenderFrameHostFromID(render_frame_host.GetGlobalId()));
const ResourceCacheKey key =
CalculateResourceCacheKeyFromCommittedFrame(render_frame_host);
auto non_hosting_it = non_hosting_frame_hosts_.find(key);
if (non_hosting_it != non_hosting_frame_hosts_.end()) {
non_hosting_it->second.erase(render_frame_host.GetGlobalId());
}
auto remote_cache_it = remote_caches_.find(key);
if (remote_cache_it != remote_caches_.end() &&
remote_cache_it->second.render_frame_host_id ==
render_frame_host.GetGlobalId()) {
remote_caches_.erase(key);
FindOrMaybeCreateResourceCache(key);
}
}
void ResourceCacheManager::RenderFrameHostStateChanged(
RenderFrameHostImpl& render_frame_host,
RenderFrameHost::LifecycleState old_state,
RenderFrameHost::LifecycleState new_state) {
if (new_state == RenderFrameHost::LifecycleState::kPendingDeletion ||
new_state == RenderFrameHost::LifecycleState::kInBackForwardCache) {
RenderFrameHostBecameIneligible(render_frame_host);
return;
}
if (old_state == RenderFrameHost::LifecycleState::kInBackForwardCache &&
new_state == RenderFrameHost::LifecycleState::kActive) {
const ResourceCacheKey key =
CalculateResourceCacheKeyFromCommittedFrame(render_frame_host);
auto remote_cache_it = remote_caches_.find(key);
if (remote_cache_it == remote_caches_.end()) {
HostResourceCache(render_frame_host, key);
} else {
InsertNonHostingRenderFrameId(key, render_frame_host.GetGlobalId());
auto* hosting_render_frame = GetEligibleRenderFrameHostFromID(
remote_cache_it->second.render_frame_host_id);
CHECK(hosting_render_frame);
PassResourceCacheRemote(*hosting_render_frame, render_frame_host);
}
return;
}
}
void ResourceCacheManager::DestroyHostObserver(
ResourceCacheHostObserver* observer) {
auto it = host_observers_.find(observer);
CHECK(it != host_observers_.end());
host_observers_.erase(it);
}
bool ResourceCacheManager::IsRenderFrameHostHostingRemoteCache(
RenderFrameHostImpl& render_frame_host) {
const ResourceCacheKey key =
CalculateResourceCacheKeyFromCommittedFrame(render_frame_host);
auto remote_cache_it = remote_caches_.find(key);
if (remote_cache_it == remote_caches_.end()) {
return false;
}
return remote_cache_it->second.render_frame_host_id ==
render_frame_host.GetGlobalId();
}
void ResourceCacheManager::InsertNonHostingRenderFrameId(
const ResourceCacheKey& key,
GlobalRenderFrameHostId render_frame_host_id) {
auto non_hosting_it = non_hosting_frame_hosts_.find(key);
if (non_hosting_it == non_hosting_frame_hosts_.end()) {
std::set<GlobalRenderFrameHostId> hosts = {render_frame_host_id};
non_hosting_frame_hosts_.emplace(key, std::move(hosts));
} else {
CHECK(!base::Contains(non_hosting_it->second, render_frame_host_id));
non_hosting_it->second.insert(render_frame_host_id);
}
}
RenderFrameHostImpl* ResourceCacheManager::FindOrMaybeCreateResourceCache(
const ResourceCacheKey& key) {
auto remote_cache_it = remote_caches_.find(key);
if (remote_cache_it != remote_caches_.end()) {
return RenderFrameHostImpl::FromID(
remote_cache_it->second.render_frame_host_id);
}
auto non_hosting_it = non_hosting_frame_hosts_.find(key);
if (non_hosting_it == non_hosting_frame_hosts_.end()) {
return nullptr;
}
RenderFrameHostImpl* new_hosting_render_frame = nullptr;
auto render_frame_host_it = non_hosting_it->second.begin();
while (render_frame_host_it != non_hosting_it->second.end()) {
RenderFrameHostImpl* render_frame_host =
GetEligibleRenderFrameHostFromID(*render_frame_host_it);
if (!render_frame_host) {
++render_frame_host_it;
continue;
}
if (!new_hosting_render_frame) {
HostResourceCache(*render_frame_host, key);
render_frame_host_it = non_hosting_it->second.erase(render_frame_host_it);
new_hosting_render_frame = render_frame_host;
continue;
}
PassResourceCacheRemote(*new_hosting_render_frame, *render_frame_host);
++render_frame_host_it;
}
return new_hosting_render_frame;
}
void ResourceCacheManager::HostResourceCache(
RenderFrameHostImpl& render_frame_host,
const ResourceCacheKey& key) {
CHECK_EQ(key.process_lock, render_frame_host.GetProcess()->GetProcessLock());
CHECK(!render_frame_host.HasPendingCommitForCrossDocumentNavigation());
mojo::Remote<blink::mojom::ResourceCache> remote =
CreateResourceCacheRemote(render_frame_host, key);
auto [it, inserted] = remote_caches_.try_emplace(
std::move(key), std::move(remote), render_frame_host.GetGlobalId());
CHECK(inserted);
auto* web_contents = static_cast<WebContentsImpl*>(
WebContentsImpl::FromRenderFrameHostID(render_frame_host.GetGlobalId()));
auto observer_it = base::ranges::find_if(
host_observers_,
[&web_contents](
const std::unique_ptr<ResourceCacheHostObserver>& observer) {
return web_contents == observer->web_contents();
});
if (observer_it == host_observers_.end()) {
host_observers_.insert(
std::make_unique<ResourceCacheHostObserver>(this, web_contents));
}
}
mojo::Remote<blink::mojom::ResourceCache>
ResourceCacheManager::CreateResourceCacheRemote(
RenderFrameHostImpl& render_frame_host,
const ResourceCacheKey& key) {
mojo::PendingRemote<blink::mojom::ResourceCache> pending_remote;
render_frame_host.GetRemoteInterfaces()->GetInterface(
pending_remote.InitWithNewPipeAndPassReceiver());
mojo::Remote<blink::mojom::ResourceCache> remote(std::move(pending_remote));
remote.set_disconnect_handler(base::BindOnce(
&ResourceCacheManager::OnRemoteCacheDisconnected,
weak_ptr_factory_.GetWeakPtr(), key, render_frame_host.GetGlobalId()));
return remote;
}
void ResourceCacheManager::OnRemoteCacheDisconnected(
const ResourceCacheKey& key,
GlobalRenderFrameHostId render_frame_host_id) {
auto it = remote_caches_.find(key);
if (it == remote_caches_.end()) {
return;
}
auto* render_frame_host = GetEligibleRenderFrameHostFromID(
render_frame_host_id, false);
if (!render_frame_host) {
remote_caches_.erase(key);
FindOrMaybeCreateResourceCache(key);
return;
}
CHECK(!render_frame_host->GetProcess()->ShutdownRequested());
mojo::Remote<blink::mojom::ResourceCache> remote =
CreateResourceCacheRemote(*render_frame_host, key);
it->second.remote = std::move(remote);
auto non_hosting_it = non_hosting_frame_hosts_.find(key);
if (non_hosting_it == non_hosting_frame_hosts_.end()) {
return;
}
for (auto& id : non_hosting_it->second) {
RenderFrameHostImpl* eligible_non_hosting_frame_host =
GetEligibleRenderFrameHostFromID(id);
if (eligible_non_hosting_frame_host) {
PassResourceCacheRemote(*render_frame_host,
*eligible_non_hosting_frame_host);
}
}
}
}