| // 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; |
| })(); |