blob: 8997086ff05241450e8d5dec1d92981d9f48a30c [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/process_internals/process_internals_handler_impl.h"
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/process_internals/process_internals.mojom.h"
#include "content/browser/process_lock.h"
#include "content/browser/renderer_host/agent_scheduling_group_host.h"
#include "content/browser/renderer_host/back_forward_cache_impl.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/navigation_entry_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
namespace content {
namespace {
using IsolatedOriginSource = ChildProcessSecurityPolicy::IsolatedOriginSource;
// Creates FrameInfoPtr but does not populate subframes.
::mojom::FrameInfoPtr RenderFrameHostToFrameInfoNoTraverse(
RenderFrameHostImpl* frame,
::mojom::FrameInfo::Type type) {
auto frame_info = ::mojom::FrameInfo::New();
frame_info->routing_id = frame->GetRoutingID();
frame_info->agent_scheduling_group_id =
frame->GetAgentSchedulingGroup().id_for_debugging();
frame_info->process_id = frame->GetProcess()->GetDeprecatedID();
frame_info->last_committed_url =
frame->GetLastCommittedURL().is_valid()
? std::make_optional(frame->GetLastCommittedURL())
: std::nullopt;
frame_info->type = type;
SiteInstanceImpl* site_instance =
static_cast<SiteInstanceImpl*>(frame->GetSiteInstance());
frame_info->site_instance = ::mojom::SiteInstanceInfo::New();
frame_info->site_instance->id = site_instance->GetId().value();
frame_info->site_instance->locked =
site_instance->GetProcess()->GetProcessLock().is_locked_to_site();
frame_info->site_instance->site_url =
site_instance->HasSite()
? std::make_optional(site_instance->GetSiteInfo().site_url())
: std::nullopt;
frame_info->site_instance->is_guest = site_instance->IsGuest();
frame_info->site_instance->is_pdf = site_instance->IsPdf();
frame_info->site_instance->is_sandbox_for_iframes =
site_instance->GetSiteInfo().is_sandboxed();
frame_info->site_instance->are_javascript_optimizers_enabled =
!site_instance->GetSiteInfo().are_v8_optimizations_disabled();
frame_info->site_instance->site_instance_group_id =
site_instance->group() ? site_instance->group()->GetId().value() : 0;
frame_info->site_instance->browsing_instance_id =
site_instance->GetBrowsingInstanceId().value();
// If the SiteInstance has a non-default StoragePartition, include a basic
// string representation of it. Skip cases where the StoragePartition is
// already conveyed in the site URL to avoid redundancy.
const auto& partition = site_instance->GetStoragePartitionConfig();
if (!partition.is_default() &&
site_instance->GetSiteInfo().site_url().spec().find(
partition.partition_domain()) == std::string::npos) {
std::string partition_description =
base::StrCat({partition.partition_domain().c_str(), "/",
partition.partition_name().c_str(),
partition.in_memory() ? "" : "?persist"});
frame_info->site_instance->storage_partition =
std::make_optional(partition_description);
}
// Only send a process lock URL if it's different from the site URL. In the
// common case they are the same, so we avoid polluting the UI with two
// identical URLs.
bool should_show_lock_url = frame_info->site_instance->locked &&
site_instance->GetSiteInfo().process_lock_url() !=
site_instance->GetSiteInfo().site_url();
frame_info->site_instance->process_lock_url =
should_show_lock_url
? std::make_optional(site_instance->GetSiteInfo().process_lock_url())
: std::nullopt;
frame_info->site_instance->requires_origin_keyed_process =
site_instance->GetSiteInfo().requires_origin_keyed_process();
return frame_info;
}
// Traverses over all subframes for the given |frame|.
::mojom::FrameInfoPtr RenderFrameHostToFrameInfo(
WebContentsImpl* web_contents,
RenderFrameHostImpl* frame,
::mojom::FrameInfo::Type type) {
std::map<RenderFrameHostImpl*, ::mojom::FrameInfo*> all_frame_info;
// Store the outermost frame info because we will need to return it and
// |all_frame_info| does not retain ownership of the mojom::FrameInfo.
::mojom::FrameInfoPtr outermost_frame_info =
RenderFrameHostToFrameInfoNoTraverse(frame, type);
all_frame_info[frame] = outermost_frame_info.get();
// Execute over all frames appending any frames encountered to the parent's
// subframe data.
frame->ForEachRenderFrameHostImplWithAction(
[web_contents, outermost_frame = frame, type,
&all_frame_info](RenderFrameHostImpl* rfh) {
// We've already handled the outermost frame outside of this.
if (rfh == outermost_frame)
return RenderFrameHost::FrameIterationAction::kContinue;
// If this is a nested WebContents skip it, it will be encountered
// by the GetAllWebContents iteration.
if (WebContents::FromRenderFrameHost(rfh) != web_contents)
return RenderFrameHost::FrameIterationAction::kSkipChildren;
::mojom::FrameInfoPtr frame_info =
RenderFrameHostToFrameInfoNoTraverse(rfh, type);
all_frame_info[rfh] = frame_info.get();
RenderFrameHostImpl* parent = rfh->GetParentOrOuterDocumentOrEmbedder();
DCHECK(base::Contains(all_frame_info, parent));
all_frame_info[parent]->subframes.push_back(std::move(frame_info));
return RenderFrameHost::FrameIterationAction::kContinue;
});
return outermost_frame_info;
}
// Adds `host` to `out_frames` if it is a prerendered main frame.
RenderFrameHost::FrameIterationAction CollectPrerenders(
WebContentsImpl* web_contents,
RenderFrameHostImpl* host,
std::vector<::mojom::FrameInfoPtr>& out_frames) {
if (!host->GetParentOrOuterDocument()) {
if (host->GetLifecycleState() ==
RenderFrameHost::LifecycleState::kPrerendering) {
out_frames.push_back(RenderFrameHostToFrameInfo(
web_contents, host, ::mojom::FrameInfo::Type::kPrerender));
}
return RenderFrameHost::FrameIterationAction::kSkipChildren;
}
return RenderFrameHost::FrameIterationAction::kContinue;
}
std::string IsolatedOriginSourceToString(IsolatedOriginSource source) {
switch (source) {
case IsolatedOriginSource::BUILT_IN:
return "Built-in";
case IsolatedOriginSource::COMMAND_LINE:
return "Command line";
case IsolatedOriginSource::FIELD_TRIAL:
return "Field trial";
case IsolatedOriginSource::POLICY:
return "Device policy";
case IsolatedOriginSource::TEST:
return "Test";
case IsolatedOriginSource::USER_TRIGGERED:
return "User-triggered";
case IsolatedOriginSource::WEB_TRIGGERED:
return "Web-triggered";
default:
NOTREACHED();
}
}
} // namespace
ProcessInternalsHandlerImpl::ProcessInternalsHandlerImpl(
BrowserContext* browser_context,
mojo::PendingReceiver<::mojom::ProcessInternalsHandler> receiver)
: browser_context_(browser_context), receiver_(this, std::move(receiver)) {}
ProcessInternalsHandlerImpl::~ProcessInternalsHandlerImpl() = default;
void ProcessInternalsHandlerImpl::GetProcessCountInfo(
GetProcessCountInfoCallback callback) {
::mojom::ProcessCountInfoPtr info = ::mojom::ProcessCountInfo::New();
info->renderer_process_count_total = RenderProcessHostImpl::GetProcessCount();
info->renderer_process_count_for_limit =
RenderProcessHostImpl::GetProcessCountForLimit();
info->renderer_process_limit =
RenderProcessHost::GetMaxRendererProcessCount();
std::move(callback).Run(std::move(info));
}
void ProcessInternalsHandlerImpl::GetIsolationMode(
GetIsolationModeCallback callback) {
std::vector<std::string_view> modes;
if (SiteIsolationPolicy::UseDedicatedProcessesForAllSites())
modes.push_back("Site Per Process");
if (SiteIsolationPolicy::AreIsolatedOriginsEnabled())
modes.push_back("Isolate Origins");
if (SiteIsolationPolicy::IsStrictOriginIsolationEnabled())
modes.push_back("Strict Origin Isolation");
if (SiteIsolationPolicy::IsSiteIsolationForCOOPEnabled())
modes.push_back("COOP");
// Retrieve any additional site isolation modes controlled by the embedder.
std::vector<std::string> additional_modes =
GetContentClient()->browser()->GetAdditionalSiteIsolationModes();
std::move(additional_modes.begin(), additional_modes.end(),
std::back_inserter(modes));
std::move(callback).Run(modes.empty() ? "Disabled"
: base::JoinString(modes, ", "));
}
void ProcessInternalsHandlerImpl::GetProcessPerSiteMode(
GetProcessPerSiteModeCallback callback) {
if (!GetContentClient()
->browser()
->ShouldAllowProcessPerSiteForMultipleMainFrames(browser_context_)) {
std::move(callback).Run("off (ContentClient policy)");
return;
}
if (!base::FeatureList::IsEnabled(
features::kProcessPerSiteUpToMainFrameThreshold)) {
std::move(callback).Run("off (feature setting)");
return;
}
std::move(callback).Run(base::StringPrintf(
"on (limit %d)", features::kProcessPerSiteMainFrameThreshold.Get()));
}
void ProcessInternalsHandlerImpl::GetUserTriggeredIsolatedOrigins(
GetUserTriggeredIsolatedOriginsCallback callback) {
// Retrieve serialized user-triggered isolated origins for the current
// profile (i.e., profile from which chrome://process-internals is shown).
// Note that this may differ from the list of stored user-triggered isolated
// origins if the user clears browsing data. Clearing browsing data clears
// stored isolated origins right away, but the corresponding origins in
// ChildProcessSecurityPolicy will stay active until next restart, and hence
// they will still be present in this list.
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
std::vector<std::string> serialized_origins;
for (const auto& origin : policy->GetIsolatedOrigins(
IsolatedOriginSource::USER_TRIGGERED, browser_context_)) {
serialized_origins.push_back(origin.Serialize());
}
std::move(callback).Run(std::move(serialized_origins));
}
void ProcessInternalsHandlerImpl::GetWebTriggeredIsolatedOrigins(
GetWebTriggeredIsolatedOriginsCallback callback) {
// Retrieve serialized user-triggered isolated origins for the current
// profile (i.e., profile from which chrome://process-internals is shown).
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
std::vector<std::string> serialized_origins;
for (const auto& origin : policy->GetIsolatedOrigins(
IsolatedOriginSource::WEB_TRIGGERED, browser_context_)) {
serialized_origins.push_back(origin.Serialize());
}
std::move(callback).Run(std::move(serialized_origins));
}
void ProcessInternalsHandlerImpl::GetGloballyIsolatedOrigins(
GetGloballyIsolatedOriginsCallback callback) {
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
std::vector<::mojom::IsolatedOriginInfoPtr> origins;
// The following global isolated origin sources are safe to show to the user.
// Any new sources should only be added here if they are ok to be shown on
// chrome://process-internals.
for (IsolatedOriginSource source :
{IsolatedOriginSource::BUILT_IN, IsolatedOriginSource::COMMAND_LINE,
IsolatedOriginSource::FIELD_TRIAL, IsolatedOriginSource::POLICY,
IsolatedOriginSource::TEST}) {
for (const auto& origin : policy->GetIsolatedOrigins(source)) {
auto info = ::mojom::IsolatedOriginInfo::New();
info->origin = origin.Serialize();
info->source = IsolatedOriginSourceToString(source);
origins.push_back(std::move(info));
}
}
std::move(callback).Run(std::move(origins));
}
void ProcessInternalsHandlerImpl::GetAllWebContentsInfo(
GetAllWebContentsInfoCallback callback) {
std::vector<::mojom::WebContentsInfoPtr> infos;
std::vector<WebContentsImpl*> all_contents =
WebContentsImpl::GetAllWebContents();
for (WebContentsImpl* web_contents : all_contents) {
// Do not return WebContents that don't belong to the current
// BrowserContext to avoid leaking data between contexts.
if (web_contents->GetBrowserContext() != browser_context_)
continue;
auto info = ::mojom::WebContentsInfo::New();
info->title = base::UTF16ToUTF8(web_contents->GetTitle());
info->root_frame = RenderFrameHostToFrameInfo(
web_contents, web_contents->GetPrimaryMainFrame(),
::mojom::FrameInfo::Type::kActive);
// Retrieve all root frames from bfcache as well.
NavigationControllerImpl& controller = web_contents->GetController();
const auto& entries = controller.GetBackForwardCache().GetEntries();
for (const auto& entry : entries) {
info->bfcached_root_frames.push_back(RenderFrameHostToFrameInfo(
web_contents, (*entry).render_frame_host(),
::mojom::FrameInfo::Type::kBackForwardCache));
}
// Retrieve prerendering root frames.
web_contents->ForEachRenderFrameHostImpl(
[web_contents, &prerender_root_frames = info->prerender_root_frames](
RenderFrameHostImpl* rfh) {
CollectPrerenders(web_contents, rfh, prerender_root_frames);
});
infos.push_back(std::move(info));
}
std::move(callback).Run(std::move(infos));
}
} // namespace content