blob: 9cb5d77278fb4fa92d3cdc3268fa390889f940ab [file] [log] [blame]
// Copyright (c) 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.
/**
* This view displays a summary of the current Reporting cache, including the
* configuration headers received for Reporting-enabled origins, and any queued
* reports that are waiting to be uploaded.
*/
var ReportingView = (function() {
'use strict';
// We inherit from DivView.
var superClass = DivView;
/**
* @constructor
*/
function ReportingView() {
assertFirstConstructorCall(ReportingView);
// Call superclass's constructor.
superClass.call(this, ReportingView.MAIN_BOX_ID);
g_browser.addReportingInfoObserver(this, true);
}
ReportingView.TAB_ID = 'tab-handle-reporting';
ReportingView.TAB_NAME = 'Reporting';
ReportingView.TAB_HASH = '#reporting';
// IDs for special HTML elements in reporting_view.html
ReportingView.MAIN_BOX_ID = 'reporting-view-tab-content';
ReportingView.DISABLED_BOX_ID = 'reporting-view-disabled-content';
ReportingView.ENABLED_BOX_ID = 'reporting-view-enabled-content';
ReportingView.CLIENTS_EMPTY_ID = 'reporting-view-clients-empty';
ReportingView.CLIENTS_TABLE_ID = 'reporting-view-clients-table';
ReportingView.CLIENTS_TBODY_ID = 'reporting-view-clients-tbody';
ReportingView.REPORTS_EMPTY_ID = 'reporting-view-reports-empty';
ReportingView.REPORTS_TABLE_ID = 'reporting-view-reports-table';
ReportingView.REPORTS_TBODY_ID = 'reporting-view-reports-tbody';
ReportingView.NEL_POLICIES_DISABLED_ID =
'reporting-view-nel-policies-disabled';
ReportingView.NEL_POLICIES_EMPTY_ID = 'reporting-view-nel-policies-empty';
ReportingView.NEL_POLICIES_TABLE_ID = 'reporting-view-nel-policies-table';
ReportingView.NEL_POLICIES_TBODY_ID = 'reporting-view-nel-policies-tbody';
cr.addSingletonGetter(ReportingView);
ReportingView.prototype = {
// Inherit the superclass's methods.
__proto__: superClass.prototype,
onLoadLogFinish: function(data) {
return this.onReportingInfoChanged(data.reportingInfo);
},
onReportingInfoChanged: function(reportingInfo) {
if (!isObject_(reportingInfo))
return false;
var enabled = !!reportingInfo.reportingEnabled;
setNodeDisplay($(ReportingView.DISABLED_BOX_ID), !enabled);
setNodeDisplay($(ReportingView.ENABLED_BOX_ID), enabled);
if (!enabled)
return true;
displayReportDetail_(ensureArray_(reportingInfo.reports));
displayClientDetail_(ensureArray_(reportingInfo.clients));
displayNELPolicyDetail_(reportingInfo.networkErrorLogging);
return true;
},
};
/**
* Displays information about each queued report in the Reporting cache.
* REQUIRES: |reportList| must be an array
*/
function displayReportDetail_(reportList) {
// Clear the existing content.
$(ReportingView.REPORTS_TBODY_ID).innerHTML = '';
var empty = reportList.length == 0;
setNodeDisplay($(ReportingView.REPORTS_EMPTY_ID), empty);
setNodeDisplay($(ReportingView.REPORTS_TABLE_ID), !empty);
if (empty)
return;
for (var i = 0; i < reportList.length; ++i) {
var report = ensureObject_(reportList[i]);
var tr = addNode($(ReportingView.REPORTS_TBODY_ID), 'tr');
var queuedNode = addNode(tr, 'td');
if (report.queued != undefined) {
var queuedDate = timeutil.convertTimeTicksToDate(report.queued);
timeutil.addNodeWithDate(queuedNode, queuedDate);
}
addNodeWithText(tr, 'td', report.url);
var statusNode = addNode(tr, 'td');
addTextNode(statusNode, report.status);
addTextNode(statusNode, ' (' + report.group);
if (report.depth !== undefined && report.depth > 0)
addTextNode(statusNode, ', depth: ' + report.depth);
if (report.attempts !== undefined && report.attempts > 0)
addTextNode(statusNode, ', attempts: ' + report.attempts);
addTextNode(statusNode, ')');
addNodeWithText(tr, 'td', report.type);
var contentNode = addNode(tr, 'td');
if (report.type == 'network-error')
displayNetworkErrorContent_(contentNode, report);
else
displayGenericReportContent_(contentNode, report);
}
}
/**
* Adds nodes to the "content" cell for a report that allow you to show a
* summary as well as collapsable detail. We will add a clickable button that
* toggles between showing and hiding the detail; its label will be `showText`
* when the detail is hidden, and `hideText` when it's visible.
*
* The result is an object containing `summary` and `detail` nodes. You can
* add whatever content you want to each of these nodes. The summary should
* be a one-liner, and will be a <span>. The detail can be as large as you
* want, and will be a <div>.
*/
function addContentSections_(contentNode, showText, hideText) {
var sections = {};
sections.summary = addNode(contentNode, 'span');
sections.summary.classList.add('reporting-content-summary');
var button = addNode(contentNode, 'span');
button.classList.add('reporting-content-expand-button');
addTextNode(button, showText);
button.onclick = function() {
toggleNodeDisplay(sections.detail);
button.textContent =
getNodeDisplay(sections.detail) ? hideText : showText;
};
sections.detail = addNode(contentNode, 'div');
sections.detail.classList.add('reporting-content-detail');
setNodeDisplay(sections.detail, false);
return sections;
}
/**
* Displays format-specific detail for Network Error Logging reports.
* REQUIRES: |report| must be an object
*/
function displayNetworkErrorContent_(contentNode, report) {
var contentSections =
addContentSections_(contentNode, 'Show raw report', 'Hide raw report');
var body = ensureObject_(report.body);
addTextNode(contentSections.summary, body.type);
// Only show the status code if it's present and not 0.
if (body.status_code)
addTextNode(
contentSections.summary, ' (' + report.body.status_code + ')');
addNodeWithText(
contentSections.detail, 'pre', JSON.stringify(report, null, ' '));
}
/**
* Displays a generic content cell for reports whose type we don't know how to
* render something specific for.
* REQUIRES: |report| must be an object
*/
function displayGenericReportContent_(contentNode, report) {
var contentSections =
addContentSections_(contentNode, 'Show raw report', 'Hide raw report');
addNodeWithText(
contentSections.detail, 'pre', JSON.stringify(report, null, ' '));
}
/**
* Displays information about each origin that has provided Reporting headers.
* REQUIRES: |clientList| must be an array
*/
function displayClientDetail_(clientList) {
// Clear the existing content.
$(ReportingView.CLIENTS_TBODY_ID).innerHTML = '';
var empty = clientList.length == 0;
setNodeDisplay($(ReportingView.CLIENTS_EMPTY_ID), empty);
setNodeDisplay($(ReportingView.CLIENTS_TABLE_ID), !empty);
if (empty)
return;
for (var i = 0; i < clientList.length; ++i) {
var client = ensureObject_(clientList[i]);
var groups = ensureArray_(client.groups);
if (groups.length == 0)
continue;
// Calculate the total number of endpoints for this origin, so that we can
// rowspan its origin cell.
var originHeight = 0;
for (var j = 0; j < groups.length; ++j) {
var group = ensureObject_(groups[j]);
var endpoints = ensureArray_(group.endpoints);
originHeight += group.endpoints.length;
}
if (originHeight == 0)
continue;
for (var j = 0; j < groups.length; ++j) {
var group = ensureObject_(groups[j]);
var endpoints = ensureArray_(group.endpoints);
for (var k = 0; k < endpoints.length; ++k) {
var endpoint = ensureObject_(endpoints[k]);
var tr = addNode($(ReportingView.CLIENTS_TBODY_ID), 'tr');
if (j == 0 && k == 0) {
var originNode = addNode(tr, 'td');
originNode.setAttribute('rowspan', originHeight);
addTextNode(originNode, client.origin);
}
if (k == 0) {
var groupNode = addNode(tr, 'td');
groupNode.setAttribute('rowspan', group.endpoints.length);
addTextNode(groupNode, group.name);
var subdomainsNode = addNode(tr, 'td');
subdomainsNode.classList.add('reporting-centered');
subdomainsNode.setAttribute('rowspan', group.endpoints.length);
addTextNode(
subdomainsNode, !!group.includeSubdomains ? 'yes' : 'no');
var expiresNode = addNode(tr, 'td');
expiresNode.setAttribute('rowspan', group.endpoints.length);
if (group.expires !== undefined) {
var expiresDate = timeutil.convertTimeTicksToDate(group.expires);
timeutil.addNodeWithDate(expiresNode, expiresDate);
if (expired_(expiresDate)) {
var expiredSpan = addNode(expiresNode, 'span');
expiredSpan.classList.add('warning-text');
addTextNode(expiredSpan, ' [expired]');
}
}
}
var endpointNode = addNode(tr, 'td');
addTextNode(endpointNode, endpoint.url);
var priorityNode = addNode(tr, 'td');
priorityNode.classList.add('reporting-centered');
addTextNode(priorityNode, valueOrDefault_(endpoint.priority, 0));
var weightNode = addNode(tr, 'td');
weightNode.classList.add('reporting-centered');
addTextNode(weightNode, valueOrDefault_(endpoint.weight, 1));
addUploadCount_(tr, ensureObject_(endpoint.successful));
addUploadCount_(tr, ensureObject_(endpoint.failed));
}
}
}
}
/**
* Adds an upload count cell to the client details table.
* REQUIRES: |counts| must be an object
*/
function addUploadCount_(tr, counts) {
var node = addNode(tr, 'td');
node.classList.add('reporting-centered');
var uploads = valueOrDefault_(counts.uploads, 0);
var reports = valueOrDefault_(counts.reports, 0);
if (uploads == 0 && reports == 0) {
addTextNode(node, '-');
} else {
addTextNode(node, uploads + ' (' + reports + ')');
}
}
/**
* Displays information about each origin that has provided NEL headers.
*/
function displayNELPolicyDetail_(nelInfo) {
// Clear the existing content.
$(ReportingView.NEL_POLICIES_TBODY_ID).innerHTML = '';
var disabled = (nelInfo === undefined);
setNodeDisplay($(ReportingView.NEL_POLICIES_DISABLED_ID), disabled);
if (disabled) {
setNodeDisplay($(ReportingView.NEL_POLICIES_EMPTY_ID), false);
setNodeDisplay($(ReportingView.NEL_POLICIES_TABLE_ID), false);
return;
}
nelInfo = ensureObject_(nelInfo);
var policies = ensureArray_(nelInfo.originPolicies);
var empty = policies.length == 0;
setNodeDisplay($(ReportingView.NEL_POLICIES_EMPTY_ID), empty);
setNodeDisplay($(ReportingView.NEL_POLICIES_TABLE_ID), !empty);
if (empty)
return;
for (var i = 0; i < policies.length; ++i) {
var policy = ensureObject_(policies[i]);
var tr = addNode($(ReportingView.NEL_POLICIES_TBODY_ID), 'tr');
addNodeWithText(tr, 'td', policy.origin);
var subdomainsNode = addNode(tr, 'td');
subdomainsNode.classList.add('reporting-centered');
addTextNode(subdomainsNode, !!policy.includeSubdomains ? 'yes' : 'no');
var expiresNode = addNode(tr, 'td');
if (policy.expires !== undefined) {
var expiresDate = timeutil.convertTimeTicksToDate(policy.expires);
timeutil.addNodeWithDate(expiresNode, expiresDate);
if (expired_(expiresDate)) {
var expiredSpan = addNode(expiresNode, 'span');
expiredSpan.classList.add('warning-text');
addTextNode(expiredSpan, ' [expired]');
}
}
addNodeWithText(tr, 'td', policy.reportTo);
var successFractionNode = addNode(tr, 'td');
successFractionNode.classList.add('reporting-right-justified');
addTextNode(successFractionNode, percent_(policy.successFraction));
var failureFractionNode = addNode(tr, 'td');
failureFractionNode.classList.add('reporting-right-justified');
addTextNode(failureFractionNode, percent_(policy.failureFraction));
}
}
/**
* Returns whether an expiry timestamp has expired. If we're viewing live
* data, uses the actual current time to determine whether it's expired. If
* we're viewing data from a saved log file, uses the timestamp when the file
* was recorded.
*
* @param {Date} expiry An expiry time
*/
function expired_(expiry) {
var now;
if (MainView.isViewingLoadedLog()) {
now = new Date(ClientInfo.numericDate);
} else {
now = new Date();
}
return expiry < now;
}
/**
* Formats a float fraction as a percentage.
*/
function percent_(fraction) {
return (valueOrDefault_(fraction, 0) * 100).toFixed(2) + '%';
}
function isObject_(value) {
return value && typeof(value) === 'object';
}
function isArray_(value) {
return value !== undefined && value instanceof Array;
}
function ensureObject_(value) {
if (isObject_(value))
return value;
return {};
}
function ensureArray_(value) {
if (isArray_(value))
return value;
return [];
}
function valueOrDefault_(value, defaultValue) {
if (value != undefined)
return value;
return defaultValue;
}
return ReportingView;
})();