0

Add information about all WebContents in a profile to process-internals.

This CL adds basic information for all WebContents in the current
browser profile. For each one, the frame tree is displayed in a
tree UI.

Bug: 850087
Change-Id: I91e88f88ad97931fb708f97e44e12d676648c507
Reviewed-on: https://chromium-review.googlesource.com/c/1265022
Commit-Queue: Nasko Oskov <nasko@chromium.org>
Reviewed-by: Demetrios Papadopoulos <dpapad@chromium.org>
Reviewed-by: Charlie Reis <creis@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Cr-Commit-Position: refs/heads/master@{#597397}
This commit is contained in:
Nasko Oskov
2018-10-06 02:02:05 +00:00
committed by Commit Bot
parent a05e57e20d
commit 2498284f74
10 changed files with 253 additions and 16 deletions

@ -8,4 +8,8 @@ mojom("mojo_bindings") {
sources = [
"process_internals.mojom",
]
deps = [
"//url/mojom:url_mojom_gurl",
]
}

@ -4,6 +4,37 @@
module mojom;
import "url/mojom/url.mojom";
// Basic information describing a SiteInstance.
struct SiteInstanceInfo {
int32 id;
// Boolean indicating whether the SiteInstance is locked to a specific URL.
// It does not indicate the granularity of the lock URL.
bool locked;
url.mojom.Url? site_url;
};
// Basic information describing a frame and all of its subframes.
struct FrameInfo {
int32 routing_id;
int32 process_id;
SiteInstanceInfo site_instance;
url.mojom.Url? last_committed_url;
array<FrameInfo> subframes;
};
// Basic information describing a WebContents object and all frames that are
// in it.
struct WebContentsInfo {
string title;
FrameInfo root_frame;
};
// Interface used by chrome://process-internals to query data from the
// browser process.
interface ProcessInternalsHandler {
@ -13,4 +44,8 @@ interface ProcessInternalsHandler {
// Returns the number of isolated origins.
GetIsolatedOriginsSize() => (uint32 size);
// Returns an array of WebContentsInfo structs for all WebContents
// associated with the profile in which this call is made.
GetAllWebContentsInfo() => (array<WebContentsInfo> infos);
};

@ -9,14 +9,48 @@
#include "base/strings/string_piece.h"
#include "content/browser/process_internals/process_internals.mojom.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/web_contents.h"
namespace content {
namespace {
::mojom::FrameInfoPtr FrameTreeNodeToFrameInfo(FrameTreeNode* ftn) {
RenderFrameHost* frame = ftn->current_frame_host();
auto frame_info = ::mojom::FrameInfo::New();
frame_info->routing_id = frame->GetRoutingID();
frame_info->process_id = frame->GetProcess()->GetID();
frame_info->last_committed_url =
frame->GetLastCommittedURL().is_valid()
? base::make_optional(frame->GetLastCommittedURL())
: base::nullopt;
SiteInstanceImpl* site_instance =
static_cast<SiteInstanceImpl*>(frame->GetSiteInstance());
frame_info->site_instance = ::mojom::SiteInstanceInfo::New();
frame_info->site_instance->id = site_instance->GetId();
frame_info->site_instance->locked = site_instance->lock_url().is_valid();
frame_info->site_instance->site_url =
site_instance->HasSite()
? base::make_optional(site_instance->GetSiteURL())
: base::nullopt;
for (size_t i = 0; i < ftn->child_count(); ++i) {
frame_info->subframes.push_back(FrameTreeNodeToFrameInfo(ftn->child_at(i)));
}
return frame_info;
}
} // namespace
ProcessInternalsHandlerImpl::ProcessInternalsHandlerImpl(
BrowserContext* browser_context,
mojo::InterfaceRequest<::mojom::ProcessInternalsHandler> request)
: binding_(this, std::move(request)) {}
: browser_context_(browser_context), binding_(this, std::move(request)) {}
ProcessInternalsHandlerImpl::~ProcessInternalsHandlerImpl() = default;
@ -38,4 +72,26 @@ void ProcessInternalsHandlerImpl::GetIsolatedOriginsSize(
std::move(callback).Run(size);
}
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 =
FrameTreeNodeToFrameInfo(web_contents->GetFrameTree()->root());
infos.push_back(std::move(info));
}
std::move(callback).Run(std::move(infos));
}
} // namespace content

@ -17,14 +17,17 @@ namespace content {
class ProcessInternalsHandlerImpl : public ::mojom::ProcessInternalsHandler {
public:
ProcessInternalsHandlerImpl(
BrowserContext* browser_context,
mojo::InterfaceRequest<::mojom::ProcessInternalsHandler> request);
~ProcessInternalsHandlerImpl() override;
// mojom::ProcessInternalsHandler overrides:
void GetIsolationMode(GetIsolationModeCallback callback) override;
void GetIsolatedOriginsSize(GetIsolatedOriginsSizeCallback callback) override;
void GetAllWebContentsInfo(GetAllWebContentsInfoCallback callback) override;
private:
BrowserContext* browser_context_;
mojo::Binding<::mojom::ProcessInternalsHandler> binding_;
DISALLOW_COPY_AND_ASSIGN(ProcessInternalsHandlerImpl);

@ -34,6 +34,7 @@ ProcessInternalsUI::ProcessInternalsUI(WebUI* web_ui)
WebUIDataSource* source =
WebUIDataSource::Create(kChromeUIProcessInternalsHost);
source->AddResourcePath("url.mojom.js", IDR_URL_MOJO_JS);
source->AddResourcePath("process_internals.js", IDR_PROCESS_INTERNALS_JS);
source->AddResourcePath("process_internals.css", IDR_PROCESS_INTERNALS_CSS);
source->AddResourcePath("process_internals.mojom.js",
@ -51,13 +52,15 @@ ProcessInternalsUI::ProcessInternalsUI(WebUI* web_ui)
ProcessInternalsUI::~ProcessInternalsUI() = default;
void ProcessInternalsUI::BindProcessInternalsHandler(
::mojom::ProcessInternalsHandlerRequest request) {
ui_handler_ =
std::make_unique<ProcessInternalsHandlerImpl>(std::move(request));
::mojom::ProcessInternalsHandlerRequest request,
RenderFrameHost* render_frame_host) {
ui_handler_ = std::make_unique<ProcessInternalsHandlerImpl>(
render_frame_host->GetSiteInstance()->GetBrowserContext(),
std::move(request));
}
void ProcessInternalsUI::OnInterfaceRequestFromFrame(
content::RenderFrameHost* render_frame_host,
RenderFrameHost* render_frame_host,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle* interface_pipe) {
// This should not be requested by subframes, so terminate the renderer if
@ -68,7 +71,7 @@ void ProcessInternalsUI::OnInterfaceRequestFromFrame(
return;
}
registry_.TryBindInterface(interface_name, interface_pipe);
registry_.TryBindInterface(interface_name, interface_pipe, render_frame_host);
}
} // namespace content

@ -32,11 +32,12 @@ class ProcessInternalsUI : public WebUIController, public WebContentsObserver {
registry_.AddInterface(std::move(binder));
}
void BindProcessInternalsHandler(
::mojom::ProcessInternalsHandlerRequest request);
::mojom::ProcessInternalsHandlerRequest request,
RenderFrameHost* render_frame_host);
private:
std::unique_ptr<::mojom::ProcessInternalsHandler> ui_handler_;
service_manager::BinderRegistry registry_;
service_manager::BinderRegistryWithArgs<content::RenderFrameHost*> registry_;
DISALLOW_COPY_AND_ASSIGN(ProcessInternalsUI);
};

@ -43,12 +43,12 @@ body {
}
.tab-header {
-webkit-border-start: 6px solid transparent;
border-inline-start: 6px solid transparent;
padding-left: 15px;
}
.tab-header.selected {
-webkit-border-start-color: rgb(78, 87, 100);
border-inline-start-color: rgb(78, 87, 100);
}
.tab-header > button {
@ -80,3 +80,7 @@ body {
padding: 20px 0 10px 0;
z-index: 1;
}
.tree-label {
user-select: text;
}

@ -6,9 +6,16 @@
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="process_internals.css">
<script src="chrome://resources/js/mojo_bindings.js"></script>
<script src="chrome://resources/js/util.js"></script>
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/mojo_bindings.js"></script>
<link rel="stylesheet" href="chrome://resources/css/tree.css">
<script src="chrome://resources/js/cr/ui.js"></script>
<script src="chrome://resources/js/cr/ui/tree.js"></script>
<link rel="stylesheet" href="process_internals.css">
<script src="url.mojom.js"></script>
<script src="process_internals.mojom.js"></script>
<script src='process_internals.js'></script>
<title>Process Model Internals</title>
@ -28,8 +35,18 @@
</div>
</div>
<div id="web-contents">
<div class="content-header">WebContents</div>
<div class="content-header">Frame Trees</div>
<div id="wc-list" class="list pages"></div>
<div id="tree-view-container">
<button id="refresh-button">Refresh</button>
<div>Legend:</div>
<div>Frame[<i>process_id</i>:<i>routing_id</i>]:
SI:<i>site_instance_id</i>, <i>whether process is locked to a
site</i>, site: <i>site_url</i> | url:
<i>last_committed_url</i>
</div>
<tree id="tree-view"></tree>
</div>
</div>
</div>
</div>

@ -57,6 +57,114 @@ function setupTabs() {
onHashChange();
}
/**
* Root of the WebContents tree.
* @type {cr.ui.Tree|null}
*/
let treeViewRoot = null;
/**
* Initialize and return |treeViewRoot|.
* @return {cr.ui.Tree} Initialized |treeViewRoot|.
*/
function getTreeViewRoot() {
if (!treeViewRoot) {
cr.ui.decorate('#tree-view', cr.ui.Tree);
treeViewRoot = /** @type {cr.ui.Tree} */ ($('tree-view'));
treeViewRoot.detail = {payload: {}, children: {}};
}
return treeViewRoot;
}
/**
* Initialize and return a tree item representing a FrameInfo object and
* recursively creates its subframe objects.
* @param {mojom.FrameInfo} frame
* @return {Array}
*/
function frameToTreeItem(frame) {
// Compose the string which will appear in the entry for this frame.
let itemLabel = `Frame[${frame.processId}:${frame.routingId}]:`;
itemLabel += ` SI:${frame.siteInstance.id}`;
if (frame.siteInstance.locked)
itemLabel += ', locked';
if (frame.siteInstance.siteUrl)
itemLabel += `, site:${frame.siteInstance.siteUrl.url}`;
if (frame.lastCommittedUrl)
itemLabel += ` | url: ${frame.lastCommittedUrl.url}`;
let item = new cr.ui.TreeItem(
{label: itemLabel, detail: {payload: {}, children: {}}});
item.mayHaveChildren_ = true;
item.expanded = true;
item.icon = '';
let frameCount = 1;
for (const subframe of frame.subframes) {
let result = frameToTreeItem(subframe);
const subItem = result[0];
const count = result[1];
frameCount += count;
item.add(subItem);
}
return [item, frameCount];
}
/**
* Initialize and return a tree item representing the WebContentsInfo object
* and contains all frames in it as a subtree.
* @param {mojom.WebContentsInfo} webContents
* @return {cr.ui.TreeItem}
*/
function webContentsToTreeItem(webContents) {
let itemLabel = 'WebContents: ';
if (webContents.title.length > 0)
itemLabel += webContents.title + ', ';
let item = new cr.ui.TreeItem(
{label: itemLabel, detail: {payload: {}, children: {}}});
item.mayHaveChildren_ = true;
item.expanded = true;
item.icon = '';
let result = frameToTreeItem(webContents.rootFrame);
const rootItem = result[0];
const count = result[1];
itemLabel += `${count} frame` + (count > 1 ? 's.' : '.');
item.label = itemLabel;
item.add(rootItem);
return item;
}
/**
* This is a callback which is invoked when the data for WebContents
* associated with the browser profile is received from the browser process.
* @param {mojom.ProcessInternalsHandler_GetAllWebContentsInfo_ResponseParams} input
*/
function populateWebContentsTab(input) {
let tree = getTreeViewRoot();
// Clear the tree first before populating it with the new content.
tree.innerText = '';
for (const webContents of input.infos) {
const item = webContentsToTreeItem(webContents);
tree.add(item);
}
}
/**
* Function which retrieves the data for all WebContents associated with the
* current browser profile. The result is passed to populateWebContentsTab.
*/
function loadWebContentsInfo() {
uiHandler.getAllWebContentsInfo().then(populateWebContentsTab);
}
document.addEventListener('DOMContentLoaded', function() {
// Setup Mojo interface to the backend.
@ -67,14 +175,19 @@ document.addEventListener('DOMContentLoaded', function() {
// Get the Site Isolation mode and populate it.
uiHandler.getIsolationMode().then((response) => {
document.getElementById('isolation-mode').innerText = response.mode;
$('isolation-mode').innerText = response.mode;
});
uiHandler.getIsolatedOriginsSize().then((response) => {
document.getElementById('isolated-origins').innerText = response.size;
$('isolated-origins').innerText = response.size;
});
// Setup the UI
// Setup the tabbed UI
setupTabs();
// Start loading the information about WebContents.
loadWebContentsInfo();
$('refresh-button').addEventListener('click', loadWebContentsInfo);
});
})();

@ -43,6 +43,7 @@
<include name="IDR_SERVICE_WORKER_INTERNALS_HTML" file="browser/resources/service_worker/serviceworker_internals.html" flattenhtml="true" allowexternalscript="true" compress="gzip" type="BINDATA" />
<include name="IDR_SERVICE_WORKER_INTERNALS_JS" file="browser/resources/service_worker/serviceworker_internals.js" flattenhtml="true" compress="gzip" type="BINDATA" />
<include name="IDR_SERVICE_WORKER_INTERNALS_CSS" file="browser/resources/service_worker/serviceworker_internals.css" flattenhtml="true" compress="gzip" type="BINDATA" />
<include name="IDR_URL_MOJO_JS" file="${root_gen_dir}/url/mojom/url.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" />
<include name="IDR_WEBRTC_INTERNALS_HTML" file="browser/resources/media/webrtc_internals.html" flattenhtml="true" allowexternalscript="true" compress="gzip" type="BINDATA" />
<include name="IDR_WEBRTC_INTERNALS_JS" file="browser/resources/media/webrtc_internals.js" flattenhtml="true" compress="gzip" type="BINDATA" />
</includes>