// Copyright 2014 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. /** * Plot a line graph of data versus time on a HTML canvas element. * * @param {HTMLCanvasElement} plotCanvas The canvas on which the line graph is * drawn. * @param {HTMLCanvasElement} legendCanvas The canvas on which the legend for * the line graph is drawn. * @param {Array} tData The time (in seconds) in the past when the * corresponding data in plots was sampled. * @param {Array<{data: Array, color: string}>} plots An * array of plots to plot on the canvas. The field 'data' of a plot is an * array of samples to be plotted as a line graph with color speficied by * the field 'color'. The elements in the 'data' array are ordered * corresponding to their sampling time in the argument 'tData'. Also, the * number of elements in the 'data' array should be the same as in the time * array 'tData' above. * @param {number} yMin Minimum bound of y-axis * @param {number} yMax Maximum bound of y-axis. * @param {integer} yPrecision An integer value representing the number of * digits of precision the y-axis data should be printed with. */ function plotLineGraph( plotCanvas, legendCanvas, tData, plots, yMin, yMax, yPrecision) { var textFont = 12 * devicePixelRatio + 'px Arial'; var textHeight = 12 * devicePixelRatio; var padding = 5 * devicePixelRatio; // Pixels var errorOffsetPixels = 15 * devicePixelRatio; var gridColor = '#ccc'; var plotCtx = plotCanvas.getContext('2d'); var size = tData.length; function drawText(ctx, text, x, y) { ctx.font = textFont; ctx.fillStyle = '#000'; ctx.fillText(text, x, y); } function printErrorText(ctx, text) { ctx.clearRect(0, 0, plotCanvas.width, plotCanvas.height); drawText(ctx, text, errorOffsetPixels, errorOffsetPixels); } if (size < 2) { printErrorText(plotCtx, loadTimeData.getString('notEnoughDataAvailableYet')); return; } for (var count = 0; count < plots.length; count++) { if (plots[count].data.length != size) { throw new Error('Mismatch in time and plot data.'); } } function valueToString(value) { if (Math.abs(value) < 1) { return Number(value).toFixed(yPrecision - 1); } else { return Number(value).toPrecision(yPrecision); } } function getTextWidth(ctx, text) { ctx.font = textFont; // For now, all text is drawn to the left of vertical lines, or centered. // Add a 2 pixel padding so that there is some spacing between the text // and the vertical line. return Math.round(ctx.measureText(text).width) + 2 * devicePixelRatio; } function getLegend(text) { return ' ' + text + ' '; } function drawHighlightText(ctx, text, x, y, color) { ctx.strokeStyle = '#000'; ctx.strokeRect(x, y - textHeight, getTextWidth(ctx, text), textHeight); ctx.fillStyle = color; ctx.fillRect(x, y - textHeight, getTextWidth(ctx, text), textHeight); ctx.fillStyle = '#fff'; ctx.fillText(text, x, y); } function drawLine(ctx, x1, y1, x2, y2, color) { ctx.save(); ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.strokeStyle = color; ctx.lineWidth = 1 * devicePixelRatio; ctx.stroke(); ctx.restore(); } // The strokeRect method of the 2d context of a plotCanvas draws a bounding // rectangle with an offset origin and greater dimensions. Hence, use this // function to draw a rect at the desired location with desired dimensions. function drawRect(ctx, x, y, width, height, color) { var offset = 1 * devicePixelRatio; drawLine(ctx, x, y, x + width - offset, y, color); drawLine(ctx, x, y, x, y + height - offset, color); drawLine(ctx, x, y + height - offset, x + width - offset, y + height - offset, color); drawLine(ctx, x + width - offset, y, x + width - offset, y + height - offset, color); } function drawLegend() { // Show a legend only if at least one individual plot has a name. var valid = false; for (var i = 0; i < plots.length; i++) { if (plots[i].name != null) { valid = true; break; } } if (!valid) { legendCanvas.hidden = true; return; } var padding = 2 * devicePixelRatio; var legendSquareSide = 12 * devicePixelRatio; var legendCtx = legendCanvas.getContext('2d'); var xLoc = padding; var yLoc = padding; // Adjust the height of the canvas before drawing on it. for (var i = 0; i < plots.length; i++) { if (plots[i].name == null) { continue; } var legendText = getLegend(plots[i].name); xLoc += legendSquareSide + getTextWidth(legendCtx, legendText) + 2 * padding; if (i < plots.length - 1) { var xLocNext = xLoc + getTextWidth(legendCtx, getLegend(plots[i + 1].name)) + legendSquareSide; if (xLocNext >= legendCanvas.width) { xLoc = padding; yLoc = yLoc + 2 * padding + textHeight; } } } legendCanvas.height = yLoc + textHeight + padding; legendCanvas.style.height = legendCanvas.height / devicePixelRatio + 'px'; xLoc = padding; yLoc = padding; // Go over the plots again, this time drawing the legends. for (var i = 0; i < plots.length; i++) { legendCtx.fillStyle = plots[i].color; legendCtx.fillRect(xLoc, yLoc, legendSquareSide, legendSquareSide); xLoc += legendSquareSide; var legendText = getLegend(plots[i].name); drawText(legendCtx, legendText, xLoc, yLoc + textHeight - 1); xLoc += getTextWidth(legendCtx, legendText) + 2 * padding; if (i < plots.length - 1) { var xLocNext = xLoc + getTextWidth(legendCtx, getLegend(plots[i + 1].name)) + legendSquareSide; if (xLocNext >= legendCanvas.width) { xLoc = padding; yLoc = yLoc + 2 * padding + textHeight; } } } } var yMinStr = valueToString(yMin); var yMaxStr = valueToString(yMax); var yHalfStr = valueToString((yMax + yMin) / 2); var yMinWidth = getTextWidth(plotCtx, yMinStr); var yMaxWidth = getTextWidth(plotCtx, yMaxStr); var yHalfWidth = getTextWidth(plotCtx, yHalfStr); var xMinStr = tData[0]; var xMaxStr = tData[size - 1]; var xMinWidth = getTextWidth(plotCtx, xMinStr); var xMaxWidth = getTextWidth(plotCtx, xMaxStr); var xOrigin = padding + Math.max(yMinWidth, yMaxWidth, Math.round(xMinWidth / 2)); var yOrigin = padding + textHeight; var width = plotCanvas.width - xOrigin - Math.floor(xMaxWidth / 2) - padding; if (width < size) { plotCanvas.width += size - width; width = size; } var height = plotCanvas.height - yOrigin - textHeight - padding; var linePlotEndMarkerWidth = 3; function drawPlots() { // Start fresh. plotCtx.clearRect(0, 0, plotCanvas.width, plotCanvas.height); // Draw the bounding rectangle. drawRect(plotCtx, xOrigin, yOrigin, width, height, gridColor); // Draw the x and y bound values. drawText(plotCtx, yMaxStr, xOrigin - yMaxWidth, yOrigin + textHeight); drawText(plotCtx, yMinStr, xOrigin - yMinWidth, yOrigin + height); drawText(plotCtx, xMinStr, xOrigin - xMinWidth / 2, yOrigin + height + textHeight); drawText(plotCtx, xMaxStr, xOrigin + width - xMaxWidth / 2, yOrigin + height + textHeight); // Draw y-level (horizontal) lines. drawLine(plotCtx, xOrigin + 1, yOrigin + height / 4, xOrigin + width - 2, yOrigin + height / 4, gridColor); drawLine(plotCtx, xOrigin + 1, yOrigin + height / 2, xOrigin + width - 2, yOrigin + height / 2, gridColor); drawLine(plotCtx, xOrigin + 1, yOrigin + 3 * height / 4, xOrigin + width - 2, yOrigin + 3 * height / 4, gridColor); // Draw half-level value. drawText(plotCtx, yHalfStr, xOrigin - yHalfWidth, yOrigin + height / 2 + textHeight / 2); // Draw the plots. var yValRange = yMax - yMin; for (var count = 0; count < plots.length; count++) { var plot = plots[count]; var yData = plot.data; plotCtx.strokeStyle = plot.color; plotCtx.lineWidth = 2; plotCtx.beginPath(); var beginPath = true; for (var i = 0; i < size; i++) { var val = yData[i]; if (typeof val === 'string') { // Stroke the plot drawn so far and begin a fresh plot. plotCtx.stroke(); plotCtx.beginPath(); beginPath = true; continue; } var xPos = xOrigin + Math.floor(i / (size - 1) * (width - 1)); var yPos = yOrigin + height - 1 - Math.round((val - yMin) / yValRange * (height - 1)); if (beginPath) { plotCtx.moveTo(xPos, yPos); // A simple move to does not print anything. Hence, draw a little // square here to mark a beginning. plotCtx.fillStyle = '#000'; plotCtx.fillRect(xPos - linePlotEndMarkerWidth, yPos - linePlotEndMarkerWidth, linePlotEndMarkerWidth * devicePixelRatio, linePlotEndMarkerWidth * devicePixelRatio); beginPath = false; } else { plotCtx.lineTo(xPos, yPos); if (i === size - 1 || typeof yData[i + 1] === 'string') { // Draw a little square to mark an end to go with the start // markers from above. plotCtx.fillStyle = '#000'; plotCtx.fillRect(xPos - linePlotEndMarkerWidth, yPos - linePlotEndMarkerWidth, linePlotEndMarkerWidth * devicePixelRatio, linePlotEndMarkerWidth * devicePixelRatio); } } } plotCtx.stroke(); } // Paint the missing time intervals with |gridColor|. // Pick one of the plots to look for missing time intervals. function drawMissingRect(start, end) { var xLeft = xOrigin + Math.floor(start / (size - 1) * (width - 1)); var xRight = xOrigin + Math.floor(end / (size - 1) * (width - 1)); plotCtx.fillStyle = gridColor; // The x offsets below are present so that the blank space starts // and ends between two valid samples. plotCtx.fillRect(xLeft + 1, yOrigin, xRight - xLeft - 2, height - 1); } var inMissingInterval = false; var intervalStart; for (var i = 0; i < size; i++) { if (typeof plots[0].data[i] === 'string') { if (!inMissingInterval) { inMissingInterval = true; // The missing interval should actually start from the previous // sample. intervalStart = Math.max(i - 1, 0); } if (i == size - 1) { // If this is the last sample, just draw missing rect. drawMissingRect(intervalStart, i); } } else if (inMissingInterval) { inMissingInterval = false; drawMissingRect(intervalStart, i); } } } function drawTimeGuide(tDataIndex) { var x = xOrigin + tDataIndex / (size - 1) * (width - 1); drawLine(plotCtx, x, yOrigin, x, yOrigin + height - 1, '#000'); drawText(plotCtx, tData[tDataIndex], x - getTextWidth(plotCtx, tData[tDataIndex]) / 2, yOrigin - 2); for (var count = 0; count < plots.length; count++) { var yData = plots[count].data; // Draw small black square on the plot where the time guide intersects // it. var val = yData[tDataIndex]; var yPos, valStr; if (typeof val === 'string') { yPos = yOrigin + Math.round(height / 2); valStr = val; } else { yPos = yOrigin + height - 1 - Math.round((val - yMin) / (yMax - yMin) * (height - 1)); valStr = valueToString(val); } plotCtx.fillStyle = '#000'; plotCtx.fillRect(x - 2, yPos - 2, 4, 4); // Draw the val to right of the intersection. var yLoc; if (yPos - textHeight / 2 < yOrigin) { yLoc = yOrigin + textHeight; } else if (yPos + textHeight / 2 >= yPos + height) { yLoc = yOrigin + height - 1; } else { yLoc = yPos + textHeight / 2; } drawHighlightText(plotCtx, valStr, x + 5, yLoc, plots[count].color); } } function onMouseOverOrMove(event) { drawPlots(); var boundingRect = plotCanvas.getBoundingClientRect(); var x = Math.round((event.clientX - boundingRect.left) * devicePixelRatio); var y = Math.round((event.clientY - boundingRect.top) * devicePixelRatio); if (x < xOrigin || x >= xOrigin + width || y < yOrigin || y >= yOrigin + height) { return; } if (width == size) { drawTimeGuide(x - xOrigin); } else { drawTimeGuide(Math.round((x - xOrigin) / (width - 1) * (size - 1))); } } function onMouseOut(event) { drawPlots(); } drawLegend(); drawPlots(); plotCanvas.addEventListener('mouseover', onMouseOverOrMove); plotCanvas.addEventListener('mousemove', onMouseOverOrMove); plotCanvas.addEventListener('mouseout', onMouseOut); } var sleepSampleInterval = 30 * 1000; // in milliseconds. var sleepText = loadTimeData.getString('systemSuspended'); var invalidDataText = loadTimeData.getString('invalidData'); var offlineText = loadTimeData.getString('offlineText'); var plotColors = ['Red', 'Blue', 'Green', 'Gold', 'CadetBlue', 'LightCoral', 'LightSlateGray', 'Peru', 'DarkRed', 'LawnGreen', 'Tan']; /** * Add canvases for plotting to |plotsDiv|. For every header in |headerArray|, * one canvas for the plot and one for its legend are added. * * @param {Array} headerArray Headers for the different plots to be * added to |plotsDiv|. * @param {HTMLDivElement} plotsDiv The div element into which the canvases * are added. * @return {: {plotCanvas: , * legendCanvas: } Returns an object * with the headers as 'keys'. Each element is an object containing the * legend canvas and the plot canvas that have been added to |plotsDiv|. */ function addCanvases(headerArray, plotsDiv) { // Remove the contents before adding new ones. while (plotsDiv.firstChild != null) { plotsDiv.removeChild(plotsDiv.firstChild); } var width = Math.floor(plotsDiv.getBoundingClientRect().width); var canvases = {}; for (var i = 0; i < headerArray.length; i++) { var header = document.createElement('h4'); header.textContent = headerArray[i]; plotsDiv.appendChild(header); var legendCanvas = document.createElement('canvas'); legendCanvas.width = width * devicePixelRatio; legendCanvas.style.width = width + 'px'; plotsDiv.appendChild(legendCanvas); var plotCanvasDiv = document.createElement('div'); plotCanvasDiv.style.overflow = 'auto'; plotsDiv.appendChild(plotCanvasDiv); plotCanvas = document.createElement('canvas'); plotCanvas.width = width * devicePixelRatio; plotCanvas.height = 200 * devicePixelRatio; plotCanvas.style.height = '200px'; plotCanvasDiv.appendChild(plotCanvas); canvases[headerArray[i]] = {plot: plotCanvas, legend: legendCanvas}; } return canvases; } /** * Add samples in |sampleArray| to individual plots in |plots|. If the system * resumed from a sleep/suspend, then "suspended" sleep samples are added to * the plot for the sleep duration. * * @param {Array<{data: Array, color: string}>} plots An * array of plots to plot on the canvas. The field 'data' of a plot is an * array of samples to be plotted as a line graph with color speficied by * the field 'color'. The elements in the 'data' array are ordered * corresponding to their sampling time in the argument 'tData'. Also, the * number of elements in the 'data' array should be the same as in the time * array 'tData' below. * @param {Array} tData The time (in seconds) in the past when the * corresponding data in plots was sampled. * @param {Array} absTime * @param {Array} sampleArray The array of samples wherein each * element corresponds to the individual plot in |plots|. * @param {number} sampleTime Time in milliseconds since the epoch when the * samples in |sampleArray| were captured. * @param {number} previousSampleTime Time in milliseconds since the epoch * when the sample prior to the current sample was captured. * @param {Array<{time: number, sleepDuration: number}>} systemResumedArray An * array objects corresponding to system resume events. The 'time' field is * for the time in milliseconds since the epoch when the system resumed. The * 'sleepDuration' field is for the time in milliseconds the system spent * in sleep/suspend state. */ function addTimeDataSample(plots, tData, absTime, sampleArray, sampleTime, previousSampleTime, systemResumedArray) { for (var i = 0; i < plots.length; i++) { if (plots[i].data.length != tData.length) { throw new Error('Mismatch in time and plot data.'); } } var time; if (tData.length == 0) { time = new Date(sampleTime); absTime[0] = sampleTime; tData[0] = time.toLocaleTimeString(); for (var i = 0; i < plots.length; i++) { plots[i].data[0] = sampleArray[i]; } return; } for (var i = 0; i < systemResumedArray.length; i++) { var resumeTime = systemResumedArray[i].time; var sleepDuration = systemResumedArray[i].sleepDuration; var sleepStartTime = resumeTime - sleepDuration; if (resumeTime < sampleTime) { if (sleepStartTime < previousSampleTime) { // This can happen if pending callbacks were handled before actually // suspending. sleepStartTime = previousSampleTime + 1000; } // Add sleep samples for every |sleepSampleInterval|. var sleepSampleTime = sleepStartTime; while (sleepSampleTime < resumeTime) { time = new Date(sleepSampleTime); absTime.push(sleepSampleTime); tData.push(time.toLocaleTimeString()); for (var j = 0; j < plots.length; j++) { plots[j].data.push(sleepText); } sleepSampleTime += sleepSampleInterval; } } } time = new Date(sampleTime); absTime.push(sampleTime); tData.push(time.toLocaleTimeString()); for (var i = 0; i < plots.length; i++) { plots[i].data.push(sampleArray[i]); } } /** * Display the battery charge vs time on a line graph. * * @param {Array<{time: number, * batteryPercent: number, * batteryDischargeRate: number, * externalPower: number}>} powerSupplyArray An array of objects * with fields representing the battery charge, time when the charge * measurement was taken, and whether there was external power connected at * that time. * @param {Array<{time: ?, sleepDuration: ?}>} systemResumedArray An array * objects with fields 'time' and 'sleepDuration'. Each object corresponds * to a system resume event. The 'time' field is for the time in * milliseconds since the epoch when the system resumed. The 'sleepDuration' * field is for the time in milliseconds the system spent in sleep/suspend * state. */ function showBatteryChargeData(powerSupplyArray, systemResumedArray) { var chargeTimeData = []; var chargeAbsTime = []; var chargePlot = [ { name: loadTimeData.getString('batteryChargePercentageHeader'), color: 'Blue', data: [] } ]; var dischargeRateTimeData = []; var dischargeRateAbsTime = []; var dischargeRatePlot = [ { name: loadTimeData.getString('dischargeRateLegendText'), color: 'Red', data: [] }, { name: loadTimeData.getString('movingAverageLegendText'), color: 'Green', data: [] }, { name: loadTimeData.getString('binnedAverageLegendText'), color: 'Blue', data: [] } ]; var minDischargeRate = 1000; // A high unrealistic number to begin with. var maxDischargeRate = -1000; // A low unrealistic number to begin with. for (var i = 0; i < powerSupplyArray.length; i++) { var j = Math.max(i - 1, 0); addTimeDataSample(chargePlot, chargeTimeData, chargeAbsTime, [powerSupplyArray[i].batteryPercent], powerSupplyArray[i].time, powerSupplyArray[j].time, systemResumedArray); var dischargeRate = powerSupplyArray[i].batteryDischargeRate; var inputSampleCount = $('sample-count-input').value; var movingAverage = 0; var k = 0; for (k = 0; k < inputSampleCount && i - k >= 0; k++) { movingAverage += powerSupplyArray[i - k].batteryDischargeRate; } // |k| will be atleast 1 because the 'min' value of the input field is 1. movingAverage /= k; var binnedAverage = 0; for (k = 0; k < inputSampleCount; k++) { var currentSampleIndex = i - i % inputSampleCount + k; if (currentSampleIndex >= powerSupplyArray.length) { break; } binnedAverage += powerSupplyArray[currentSampleIndex].batteryDischargeRate; } binnedAverage /= k; minDischargeRate = Math.min(dischargeRate, minDischargeRate); maxDischargeRate = Math.max(dischargeRate, maxDischargeRate); addTimeDataSample(dischargeRatePlot, dischargeRateTimeData, dischargeRateAbsTime, [dischargeRate, movingAverage, binnedAverage], powerSupplyArray[i].time, powerSupplyArray[j].time, systemResumedArray); } if (minDischargeRate == maxDischargeRate) { // This means that all the samples had the same value. Hence, offset the // extremes by a bit so that the plot looks good. minDischargeRate -= 1; maxDischargeRate += 1; } plotsDiv = $('battery-charge-plots-div'); canvases = addCanvases( [loadTimeData.getString('batteryChargePercentageHeader'), loadTimeData.getString('batteryDischargeRateHeader')], plotsDiv); batteryChargeCanvases = canvases[ loadTimeData.getString('batteryChargePercentageHeader')]; plotLineGraph( batteryChargeCanvases['plot'], batteryChargeCanvases['legend'], chargeTimeData, chargePlot, 0.00, 100.00, 3); dischargeRateCanvases = canvases[ loadTimeData.getString('batteryDischargeRateHeader')]; plotLineGraph( dischargeRateCanvases['plot'], dischargeRateCanvases['legend'], dischargeRateTimeData, dischargeRatePlot, minDischargeRate, maxDischargeRate, 3); } /** * Shows state occupancy data (CPU idle or CPU freq state occupancy) on a set of * plots on the about:power UI. * * @param {Array}>} timeInStateData Array of arrays * where each array corresponds to a CPU on the system. The elements of the * individual arrays contain state occupancy samples. * @param {Array<{time: ?, sleepDuration: ?}>} systemResumedArray An array * objects with fields 'time' and 'sleepDuration'. Each object corresponds * to a system resume event. The 'time' field is for the time in * milliseconds since the epoch when the system resumed. The 'sleepDuration' * field is for the time in milliseconds the system spent in sleep/suspend * state. * @param {string} i18nHeaderString The header string to be displayed with each * plot. For example, CPU idle data will have its own header format, and CPU * freq data will have its header format. * @param {string} unitString This is the string capturing the unit, if any, * for the different states. Note that this is not the unit of the data * being plotted. * @param {HTMLDivElement} plotsDivId The div element in which the plots should * be added. */ function showStateOccupancyData(timeInStateData, systemResumedArray, i18nHeaderString, unitString, plotsDivId) { var cpuPlots = []; for (var cpu = 0; cpu < timeInStateData.length; cpu++) { var cpuData = timeInStateData[cpu]; if (cpuData.length == 0) { cpuPlots[cpu] = {plots: [], tData: []}; continue; } tData = []; absTime = []; // Each element of |plots| is an array of samples, one for each of the CPU // states. The number of states is dicovered by looking at the first // sample for which the CPU is online. var plots = []; var stateIndexMap = []; var stateCount = 0; for (var i = 0; i < cpuData.length; i++) { if (cpuData[i].cpuOnline) { for (var state in cpuData[i].timeInState) { var stateName = state; if (unitString != null) { stateName += ' ' + unitString; } plots.push({ name: stateName, data: [], color: plotColors[stateCount] }); stateIndexMap.push(state); stateCount += 1; } break; } } // If stateCount is 0, then it means the CPU has been offline // throughout. Just add a single plot for such a case. if (stateCount == 0) { plots.push({ name: null, data: [], color: null }); stateCount = 1; // Some invalid state! } // Pass the samples through the function addTimeDataSample to add 'sleep' // samples. for (var i = 0; i < cpuData.length; i++) { var sample = cpuData[i]; var valArray = []; for (var j = 0; j < stateCount; j++) { if (sample.cpuOnline) { valArray[j] = sample.timeInState[stateIndexMap[j]]; } else { valArray[j] = offlineText; } } var k = Math.max(i - 1, 0); addTimeDataSample(plots, tData, absTime, valArray, sample.time, cpuData[k].time, systemResumedArray); } // Calculate the percentage occupancy of each state. A valid number is // possible only if two consecutive samples are valid/numbers. for (var k = 0; k < stateCount; k++) { var stateData = plots[k].data; // Skip the first sample as there is no previous sample. for (var i = stateData.length - 1; i > 0; i--) { if (typeof stateData[i] === 'number') { if (typeof stateData[i - 1] === 'number') { stateData[i] = (stateData[i] - stateData[i - 1]) / (absTime[i] - absTime[i - 1]) * 100; } else { stateData[i] = invalidDataText; } } } } // Remove the first sample from the time and data arrays. tData.shift(); for (var k = 0; k < stateCount; k++) { plots[k].data.shift(); } cpuPlots[cpu] = {plots: plots, tData: tData}; } headers = []; for (var cpu = 0; cpu < timeInStateData.length; cpu++) { headers[cpu] = 'CPU ' + cpu + ' ' + loadTimeData.getString(i18nHeaderString); } canvases = addCanvases(headers, $(plotsDivId)); for (var cpu = 0; cpu < timeInStateData.length; cpu++) { cpuCanvases = canvases[headers[cpu]]; plotLineGraph(cpuCanvases['plot'], cpuCanvases['legend'], cpuPlots[cpu]['tData'], cpuPlots[cpu]['plots'], 0, 100, 3); } } function showCpuIdleData(idleStateData, systemResumedArray) { showStateOccupancyData(idleStateData, systemResumedArray, 'idleStateOccupancyPercentageHeader', null, 'cpu-idle-plots-div'); } function showCpuFreqData(freqStateData, systemResumedArray) { showStateOccupancyData(freqStateData, systemResumedArray, 'frequencyStateOccupancyPercentageHeader', 'MHz', 'cpu-freq-plots-div'); } function requestBatteryChargeData() { chrome.send('requestBatteryChargeData'); } function requestCpuIdleData() { chrome.send('requestCpuIdleData'); } function requestCpuFreqData() { chrome.send('requestCpuFreqData'); } /** * Return a callback for the 'Show'/'Hide' buttons for each section of the * about:power page. * * @param {string} sectionId The ID of the section which is to be shown or * hidden. * @param {string} buttonId The ID of the 'Show'/'Hide' button. * @param {function} requestFunction The function which should be invoked on * 'Show' to request for data from chrome. * @return {function} The button callback function. */ function showHideCallback(sectionId, buttonId, requestFunction) { return function() { if ($(sectionId).hidden) { $(sectionId).hidden = false; $(buttonId).textContent = loadTimeData.getString('hideButton'); requestFunction(); } else { $(sectionId).hidden = true; $(buttonId).textContent = loadTimeData.getString('showButton'); } } } var powerUI = { showBatteryChargeData: showBatteryChargeData, showCpuIdleData: showCpuIdleData, showCpuFreqData: showCpuFreqData }; document.addEventListener('DOMContentLoaded', function() { $('battery-charge-section').hidden = true; $('battery-charge-show-button').onclick = showHideCallback( 'battery-charge-section', 'battery-charge-show-button', requestBatteryChargeData); $('battery-charge-reload-button').onclick = requestBatteryChargeData; $('sample-count-input').onclick = requestBatteryChargeData; $('cpu-idle-section').hidden = true; $('cpu-idle-show-button').onclick = showHideCallback( 'cpu-idle-section', 'cpu-idle-show-button', requestCpuIdleData); $('cpu-idle-reload-button').onclick = requestCpuIdleData; $('cpu-freq-section').hidden = true; $('cpu-freq-show-button').onclick = showHideCallback( 'cpu-freq-section', 'cpu-freq-show-button', requestCpuFreqData); $('cpu-freq-reload-button').onclick = requestCpuFreqData; });