|  | // 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().IsLockedToSite(); | 
|  | 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().GetProcessLockURL() != | 
|  | site_instance->GetSiteInfo().site_url(); | 
|  | frame_info->site_instance->process_lock_url = | 
|  | should_show_lock_url | 
|  | ? std::make_optional(site_instance->GetSiteInfo().GetProcessLockURL()) | 
|  | : std::nullopt; | 
|  |  | 
|  | frame_info->site_instance->requires_origin_keyed_process = | 
|  | site_instance->GetSiteInfo().agent_cluster_key().IsOriginKeyed(); | 
|  |  | 
|  | 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::GetCount(); | 
|  | info->live_renderer_processes_count_total = | 
|  | RenderProcessHostImpl::GetLiveCount(); | 
|  | 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::AreOriginKeyedProcessesEnabledByDefault()) { | 
|  | modes.push_back("Origin Keyed Processes by Default"); | 
|  | } | 
|  | 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 |