blob: b503b8efc2c3209956ea84420479849455986d7b [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(function() {
'use strict';
/**
* Reference to the backend providing all the data.
* @type {mojom.ProcessInternalsHandlerRemote}
*/
let pageHandler = null;
/**
* @param {string} id Tab id.
* @return {boolean} True if successful.
*/
function selectTab(id) {
const tabContents = document.querySelectorAll('#content > div');
const tabHeaders = $('navigation').querySelectorAll('.tab-header');
let found = false;
for (let i = 0; i < tabContents.length; i++) {
const tabContent = tabContents[i];
const tabHeader = tabHeaders[i];
const isTargetTab = tabContent.id === id;
found = found || isTargetTab;
tabContent.classList.toggle('selected', isTargetTab);
tabHeader.classList.toggle('selected', isTargetTab);
}
if (!found) {
return false;
}
window.location.hash = id;
return true;
}
function onHashChange() {
const hash = window.location.hash.slice(1).toLowerCase();
if (!selectTab(hash)) {
selectTab('general');
}
}
function setupTabs() {
const tabContents = document.querySelectorAll('#content > div');
for (let i = 0; i < tabContents.length; i++) {
const tabContent = tabContents[i];
const tabName = tabContent.querySelector('.content-header').textContent;
const tabHeader = document.createElement('div');
tabHeader.className = 'tab-header';
const button = document.createElement('button');
button.textContent = tabName;
tabHeader.appendChild(button);
tabHeader.addEventListener('click', selectTab.bind(null, tabContent.id));
$('navigation').appendChild(tabHeader);
}
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}`;
}
const 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) {
const 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 + ', ';
}
const item = new cr.ui.TreeItem(
{label: itemLabel, detail: {payload: {}, children: {}}});
item.mayHaveChildren_ = true;
item.expanded = true;
item.icon = '';
const 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) {
const 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() {
pageHandler.getAllWebContentsInfo().then(populateWebContentsTab);
}
/**
* Function which retrieves the currently active isolated origins and inserts
* them into the page. It organizes these origins into two lists: persisted
* isolated origins, which are triggered by password entry and apply only
* within the current profile, and global isolated origins, which apply to all
* profiles.
*/
function loadIsolatedOriginInfo() {
// Retrieve any persistent isolated origins for the current profile. Insert
// them into a list on the page if there is at least one such origin.
pageHandler.getUserTriggeredIsolatedOrigins().then((response) => {
const originCount = response.isolatedOrigins.length;
if (!originCount) {
return;
}
$('user-triggered-isolated-origins').textContent =
'The following origins are isolated because you previously typed a ' +
'password into these sites (' + originCount + ' total). ' +
'Clear cookies or history to wipe this list; this takes effect ' +
'after a restart.';
const list = document.createElement('ul');
for (const origin of response.isolatedOrigins) {
const item = document.createElement('li');
item.textContent = origin;
list.appendChild(item);
}
$('user-triggered-isolated-origins').appendChild(list);
});
// Retrieve global isolated origins and insert them into a separate list if
// there is at least one such origin. Since these origins may come from
// multiple sources, include the source info for each origin in parens.
pageHandler.getGloballyIsolatedOrigins().then((response) => {
const originCount = response.isolatedOrigins.length;
if (!originCount) {
return;
}
$('global-isolated-origins').textContent =
'The following origins are isolated by default for all users (' +
originCount + ' total). A description of how each origin was ' +
' activated is provided in parentheses.';
const list = document.createElement('ul');
for (const originInfo of response.isolatedOrigins) {
const item = document.createElement('li');
item.textContent = `${originInfo.origin} (${originInfo.source})`;
list.appendChild(item);
}
$('global-isolated-origins').appendChild(list);
});
}
document.addEventListener('DOMContentLoaded', function() {
// Setup Mojo interface to the backend.
pageHandler = mojom.ProcessInternalsHandler.getRemote();
// Get the Site Isolation mode and populate it.
pageHandler.getIsolationMode().then((response) => {
$('isolation-mode').innerText = response.mode;
});
loadIsolatedOriginInfo();
// Setup the tabbed UI
setupTabs();
// Start loading the information about WebContents.
loadWebContentsInfo();
$('refresh-button').addEventListener('click', loadWebContentsInfo);
});
})();