Bug 1307227 - Update the profiler's popup code to work in Gecko; r=julienw

This commit represents the changes needed to convert the existing
Gecko Profiler Add-on code to work outside of the WebExtensions
environment. A following commit will actually wire it into the
rest of the browser.

Differential Revision: https://phabricator.services.mozilla.com/D31627

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Greg Tatum 2019-05-31 15:53:46 +00:00
Родитель f59b172f85
Коммит ac6c65f4d0
9 изменённых файлов: 414 добавлений и 405 удалений

Просмотреть файл

@ -29,6 +29,12 @@ devtools.jar:
content/performance/index.xul (performance/index.xul)
content/performance-new/index.xhtml (performance-new/index.xhtml)
content/performance-new/frame-script.js (performance-new/frame-script.js)
content/performance-new/popup/background.jsm (performance-new/popup/background.jsm)
content/performance-new/popup/icons/capture-profile-icon.svg (performance-new/popup/icons/capture-profile-icon.svg)
content/performance-new/popup/initializer.js (performance-new/popup/initializer.js)
content/performance-new/popup/popup.css (performance-new/popup/popup.css)
content/performance-new/popup/popup.html (performance-new/popup/popup.html)
content/performance-new/popup/popup.js (performance-new/popup/popup.js)
content/memory/index.xhtml (memory/index.xhtml)
content/framework/toolbox-window.xul (framework/toolbox-window.xul)
content/framework/toolbox-options.xhtml (framework/toolbox-options.xhtml)

Просмотреть файл

@ -6,6 +6,7 @@
DIRS += [
'components',
'store',
'popup',
]
DevToolsModules(

Просмотреть файл

@ -0,0 +1,3 @@
# Profiler Popup
This directory controls the creation of a popup widget that can be used to record performance profiles. It is slightly different than the rest of the DevTools code, as it can be loaded independently of the rest of DevTools. The instrumentation from DevTools adds significant overhead to profiles, so this recording popup (and its shortcuts) enable a low-overhead profiling experience. This button can be enabled via the Tools -> Web Developer menu.

Просмотреть файл

@ -1,116 +1,108 @@
/* global browser */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const DEFAULT_VIEWER_URL = "https://profiler.firefox.com";
/**
* This file contains all of the background logic for controlling the state and
* configuration of the profiler. It is in a JSM so that the logic can be shared
* with both the popup client, and the keyboard shortcuts. The shortcuts don't need
* access to any UI, and need to be loaded independent of the popup.
*/
// The following are not lazily loaded as they are needed during initialization.f
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
const Loader = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
const { loader } = Loader;
// The following utilities are lazily loaded as they are not needed when controlling the
// global state of the profiler, and only are used during specific funcationality like
// symbolication or capturing a profile.
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
ChromeUtils.defineModuleGetter(this, "ProfilerGetSymbols",
"resource://gre/modules/ProfilerGetSymbols.jsm");
loader.lazyRequireGetter(this, "receiveProfile",
"devtools/client/performance-new/browser", true);
// This pref contains the JSON serialization of the popup's profiler state with
// a string key based off of the debug name and breakpad id.
const PROFILER_STATE_PREF = "devtools.performance.popup";
const DEFAULT_WINDOW_LENGTH = 20; // 20sec
const tabToConnectionMap = new Map();
// This Map caches the symbols from the shared libraries.
const symbolCache = new Map();
var profilerState;
var profileViewerURL = DEFAULT_VIEWER_URL;
var isMigratedToNewUrl;
const primeSymbolStore = libs => {
for (const {path, debugName, debugPath, breakpadId} of libs) {
symbolCache.set(`${debugName}/${breakpadId}`, {path, debugPath});
}
};
const state = intializeState();
function adjustState(newState) {
// Deep clone the object, since this can be called through popup.html,
// which can be unloaded thus leaving this object dead.
newState = JSON.parse(JSON.stringify(newState));
Object.assign(window.profilerState, newState);
browser.storage.local.set({ profilerState: window.profilerState });
}
Object.assign(state, newState);
function makeProfileAvailableToTab(profile, port) {
port.postMessage({ type: "ProfilerConnectToPage", payload: profile });
port.onMessage.addListener(async message => {
if (message.type === "ProfilerGetSymbolTable") {
const { debugName, breakpadId } = message;
try {
const [
addresses,
index,
buffer,
] = await browser.geckoProfiler.getSymbols(debugName, breakpadId);
port.postMessage({
type: "ProfilerGetSymbolTableReply",
status: "success",
result: [addresses, index, buffer],
debugName,
breakpadId,
});
} catch (e) {
port.postMessage({
type: "ProfilerGetSymbolTableReply",
status: "error",
error: `${e}`,
debugName,
breakpadId,
});
}
}
});
}
async function createAndWaitForTab(url) {
const tabPromise = browser.tabs.create({
active: true,
url,
});
return tabPromise;
}
function getProfilePreferablyAsArrayBuffer() {
// This is a compatibility wrapper for Firefox builds from before 1362800
// landed. We can remove it once Nightly switches to 56.
if ("getProfileAsArrayBuffer" in browser.geckoProfiler) {
return browser.geckoProfiler.getProfileAsArrayBuffer();
try {
Services.prefs.setStringPref(PROFILER_STATE_PREF,
JSON.stringify(state));
} catch (error) {
console.error("Unable to save the profiler state for the popup.");
throw error;
}
return browser.geckoProfiler.getProfile();
}
function getSymbols(debugName, breakpadId) {
if (symbolCache.size === 0) {
primeSymbolStore(Services.profiler.sharedLibraries);
}
const cachedLibInfo = symbolCache.get(`${debugName}/${breakpadId}`);
if (!cachedLibInfo) {
throw new Error(
`The library ${debugName} ${breakpadId} is not in the ` +
"Services.profiler.sharedLibraries list, so the local path for it is not known " +
"and symbols for it can not be obtained. This usually happens if a content " +
"process uses a library that's not used in the parent process - " +
"Services.profiler.sharedLibraries only knows about libraries in the " +
"parent process.");
}
const {path, debugPath} = cachedLibInfo;
if (!OS.Path.split(path).absolute) {
throw new Error(
"Services.profiler.sharedLibraries did not contain an absolute path for " +
`the library ${debugName} ${breakpadId}, so symbols for this library can not ` +
"be obtained.");
}
return ProfilerGetSymbols.getSymbolTable(path, debugPath, breakpadId);
}
async function captureProfile() {
if (!state.isRunning) {
// The profiler is not active, ignore this shortcut.
return;
}
// Pause profiler before we collect the profile, so that we don't capture
// more samples while the parent process waits for subprocess profiles.
await browser.geckoProfiler.pause().catch(() => {});
Services.profiler.PauseSampling();
const profilePromise = getProfilePreferablyAsArrayBuffer().catch(
const profile = await Services.profiler.getProfileDataAsArrayBuffer().catch(
e => {
console.error(e);
return {};
}
);
const tabOpenPromise = createAndWaitForTab(profileViewerURL + "/from-addon");
receiveProfile(profile, getSymbols);
try {
const [profile, tab] = await Promise.all([profilePromise, tabOpenPromise]);
const connection = tabToConnectionMap.get(tab.id);
if (connection) {
// If, for instance, it takes a long time to load the profile,
// then our onDOMContentLoaded handler and our runtime.onConnect handler
// have already connected to the page. All we need to do then is
// provide the profile.
makeProfileAvailableToTab(profile, connection.port);
} else {
// If our onDOMContentLoaded handler and our runtime.onConnect handler
// haven't connected to the page, set this so that they'll have a
// profile they can provide once they do.
tabToConnectionMap.set(tab.id, { profile });
}
} catch (e) {
console.error(e);
// const { tab } = await tabOpenPromise;
// TODO data URL doesn't seem to be working. Permissions issue?
// await browser.tabs.update(tab.id, { url: `data:text/html,${encodeURIComponent(e.toString)}` });
}
try {
await browser.geckoProfiler.resume();
} catch (e) {
console.error(e);
}
Services.profiler.ResumeSampling();
}
/**
@ -122,185 +114,152 @@ function getEnabledFeatures(features, threads) {
if (threads.length > 0) {
enabledFeatures.push("threads");
}
const supportedFeatures = Object.values(
browser.geckoProfiler.ProfilerFeature
);
const supportedFeatures = Services.profiler.GetFeatures([]);
return enabledFeatures.filter(feature => supportedFeatures.includes(feature));
}
async function startProfiler() {
const settings = window.profilerState;
const threads = settings.threads.split(",");
const options = {
bufferSize: settings.buffersize,
interval: settings.interval,
features: getEnabledFeatures(settings.features, threads),
threads,
};
if (
browser.geckoProfiler.supports &&
browser.geckoProfiler.supports.WINDOWLENGTH
) {
options.windowLength =
settings.windowLength !== settings.infiniteWindowLength
? settings.windowLength
: 0;
}
await browser.geckoProfiler.start(options);
function startProfiler() {
const threads = state.threads.split(",");
const features = getEnabledFeatures(state.features, threads);
const windowLength = state.windowLength !== state.infiniteWindowLength
? state.windowLength
: 0;
const { buffersize, interval } = state;
Services.profiler.StartProfiler(buffersize, interval, features, threads, windowLength);
}
async function stopProfiler() {
await browser.geckoProfiler.stop();
Services.profiler.StopProfiler();
}
/* exported restartProfiler */
async function restartProfiler() {
await stopProfiler();
await startProfiler();
function toggleProfiler() {
if (state.isRunning) {
stopProfiler();
} else {
startProfiler();
}
}
(async () => {
const storageResults = await browser.storage.local.get({
profilerState: null,
profileViewerURL: DEFAULT_VIEWER_URL,
// This value is to check whether or not folks have been migrated from
// perf-html.io to profiler.firefox.com
isMigratedToNewUrl: false,
});
function restartProfiler() {
stopProfiler();
startProfiler();
}
// Assign to global variables:
window.profilerState = storageResults.profilerState;
window.profileViewerURL = storageResults.profileViewerURL;
// This running observer was adapted from the web extension.
const isRunningObserver = {
_observers: new Set(),
if (!storageResults.isMigratedToNewUrl) {
if (window.profileViewerURL.startsWith("https://perf-html.io")) {
// This user needs to be migrated from perf-html.io to profiler.firefox.com.
// This is only done one time.
window.profileViewerURL = DEFAULT_VIEWER_URL;
}
// Store the fact that this migration check has been done, and optionally update
// the url if it was changed.
await browser.storage.local.set({
isMigratedToNewUrl: true,
profileViewerURL: window.profileViewerURL,
});
}
if (!window.profilerState) {
window.profilerState = {};
const features = {
java: false,
js: true,
leaf: true,
mainthreadio: false,
memory: false,
privacy: false,
responsiveness: true,
screenshots: false,
seqstyle: false,
stackwalk: true,
tasktracer: false,
trackopts: false,
jstracer: false,
};
const platform = await browser.runtime.getPlatformInfo();
switch (platform.os) {
case "mac":
// Screenshots are currently only working on mac.
features.screenshots = true;
break;
case "android":
// Java profiling is only meaningful on android.
features.java = true;
observe(subject, topic, data) {
switch (topic) {
case "profiler-started":
case "profiler-stopped":
// Make the observer calls asynchronous.
const isRunningPromise = Promise.resolve(topic === "profiler-started");
for (const observer of this._observers) {
isRunningPromise.then(observer);
}
break;
}
},
adjustState({
isRunning: false,
settingsOpen: false,
buffersize: 10000000, // 90MB
windowLength: DEFAULT_WINDOW_LENGTH,
interval: 1,
features,
threads: "GeckoMain,Compositor",
});
} else if (window.profilerState.windowLength === undefined) {
// We have `windowprofilerState` but no `windowLength`.
// That means we've upgraded the gecko profiler addon from an older version.
// Adding the default window legth in that case.
adjustState({
windowLength: DEFAULT_WINDOW_LENGTH,
});
_startListening() {
Services.obs.addObserver(this, "profiler-started");
Services.obs.addObserver(this, "profiler-stopped");
},
_stopListening() {
Services.obs.removeObserver(this, "profiler-started");
Services.obs.removeObserver(this, "profiler-stopped");
},
addObserver(observer) {
if (this._observers.size === 0) {
this._startListening();
}
this._observers.add(observer);
// Notify the observers the current state asynchronously.
Promise.resolve(Services.profiler.IsActive()).then(observer);
},
removeObserver(observer) {
if (this._observers.delete(observer) && this._observers.size === 0) {
this._stopListening();
}
},
};
function getStoredStateOrNull() {
// Pull out the stored state from preferences, it is a raw string.
const storedStateString = Services.prefs.getStringPref(PROFILER_STATE_PREF, "");
if (storedStateString === "") {
return null;
}
browser.geckoProfiler.onRunning.addListener(isRunning => {
adjustState({ isRunning });
try {
// Attempt to parse the results.
return JSON.parse(storedStateString);
} catch (error) {
console.error(`Could not parse the stored state for the profile in the ` +
`preferences ${PROFILER_STATE_PREF}`);
}
return null;
}
// With "path: null" we'll get the default icon for the browser action, which
// is theme-aware.
// The on state does not need to be theme-aware because we want to highlight
// the icon in blue regardless of whether a dark or a light theme is in use.
browser.browserAction.setIcon({
path: isRunning ? "icons/toolbar_on.png" : null,
});
function intializeState() {
const storedState = getStoredStateOrNull();
if (storedState) {
return storedState;
}
browser.browserAction.setTitle({
title: isRunning ? "Gecko Profiler (on)" : null,
});
const features = {
java: false,
js: true,
leaf: true,
mainthreadio: false,
memory: false,
privacy: false,
responsiveness: true,
screenshots: false,
seqstyle: false,
stackwalk: true,
tasktracer: false,
trackopts: false,
jstracer: false,
};
for (const popup of browser.extension.getViews({ type: "popup" })) {
popup.renderState(window.profilerState);
}
});
if (AppConstants.platform === "android") {
// Java profiling is only meaningful on android.
features.java = true;
}
browser.storage.onChanged.addListener(changes => {
if (changes.profileViewerURL) {
profileViewerURL = changes.profileViewerURL.newValue;
}
});
return {
isRunning: false,
settingsOpen: false,
buffersize: 10000000, // 90MB
windowLength: DEFAULT_WINDOW_LENGTH,
interval: 1,
features,
threads: "GeckoMain,Compositor",
};
}
browser.commands.onCommand.addListener(command => {
if (command === "ToggleProfiler") {
if (window.profilerState.isRunning) {
stopProfiler();
} else {
startProfiler();
}
} else if (command === "CaptureProfile") {
if (window.profilerState.isRunning) {
captureProfile();
}
}
});
isRunningObserver.addObserver(isRunning => {
adjustState({ isRunning });
});
browser.runtime.onConnect.addListener(port => {
const tabId = port.sender.tab.id;
const connection = tabToConnectionMap.get(tabId);
if (connection && connection.profile) {
makeProfileAvailableToTab(connection.profile, port);
} else {
tabToConnectionMap.set(tabId, { port });
}
});
const platform = AppConstants.platform;
browser.tabs.onRemoved.addListener(tabId => {
tabToConnectionMap.delete(tabId);
});
browser.webNavigation.onDOMContentLoaded.addListener(
async ({ frameId, tabId, url }) => {
if (frameId !== 0) {
return;
}
if (url.startsWith(profileViewerURL)) {
browser.tabs.executeScript(tabId, { file: "content.js" });
} else {
// As soon as we navigate away from the profile report, clean
// this up so we don't leak it.
tabToConnectionMap.delete(tabId);
}
}
);
})();
var EXPORTED_SYMBOLS = [
"adjustState",
"captureProfile",
"state",
"startProfiler",
"stopProfiler",
"restartProfiler",
"toggleProfiler",
"isRunningObserver",
"platform",
];

Просмотреть файл

@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* Initialize the require function through a BrowserLoader. This loader ensures
* that the popup can use require and has access to the window object.
*/
const { BrowserLoader } =
ChromeUtils.import("resource://devtools/client/shared/browser-loader.js");
const { require } = BrowserLoader({
baseURI: "resource://devtools/client/performance-new/popup/",
window,
});
/**
* The background.jsm manages the profiler state, and can be loaded multiple time
* for various components. This pop-up needs a copy, and it is also used by the
* profiler shortcuts. In order to do this, the background code needs to live in a
* JSM module, that can be shared with the DevTools keyboard shortcut manager.
*/
/**
* Require the popup code, and initialize it.
*/
const { initializePopup } = require("./popup");
document.addEventListener("DOMContentLoaded", () => {
initializePopup();
});

Просмотреть файл

@ -0,0 +1,13 @@
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'background.jsm',
'initializer.js',
'popup.js',
)
with Files('**'):
BUG_COMPONENT = ('Core', 'Gecko Profiler')

Просмотреть файл

@ -1,3 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
html {
background: rgb(250,250,250);
font: message-box;
@ -283,11 +287,6 @@ kbd {
flex-flow: row nowrap;
}
body.no-windowlength .settings-setting-label.windowlength,
body.no-windowlength .range-with-value.windowlength {
display: none;
}
.range-input {
margin: 0;
width: 0;

Просмотреть файл

@ -1,3 +1,6 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE html>
<html class="status-running">
<head>
@ -173,6 +176,6 @@
<section class="settings-apply-button-wrapper"><input type="button" class="settings-apply-button" value="Apply (Restart Profiler)"></section>
</section>
<script src="popup.js"></script>
<script src="initializer.js"></script>
</body>
</html>

Просмотреть файл

@ -1,4 +1,11 @@
/* global browser */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const background =
ChromeUtils.import("resource://devtools/client/performance-new/popup/background.jsm");
const intervalScale = makeExponentialScale(0.01, 100);
const buffersizeScale = makeExponentialScale(10000, 100000000);
// Window Length accepts a numerical value between 1-N. We also need to put an
@ -44,6 +51,105 @@ const threadMap = {
"style-thread": "StyleThread",
};
function initializePopup() {
const updateIsRunning = () => {
renderState(background.state);
};
background.isRunningObserver.addObserver(updateIsRunning);
window.addEventListener("unload", () => {
background.isRunningObserver.removeObserver(updateIsRunning);
});
for (const name of features) {
setupFeatureCheckbox(name);
}
for (const name in threadMap) {
setupThreadCheckbox(name);
}
document
.querySelector("#perf-settings-thread-text")
.addEventListener("change", async e => {
background.adjustState({ threads: e.target.value });
renderState(background.state);
});
document
.querySelector(".settings-apply-button")
.addEventListener("click", async () => {
background.restartProfiler();
});
document.querySelector(".button-start").addEventListener("click", async () => {
background.startProfiler();
background.adjustState({ isRunning: true });
renderState(background.state);
});
document.querySelector(".button-cancel").addEventListener("click", async () => {
background.stopProfiler();
background.adjustState({ isRunning: false });
renderState(background.state);
});
document
.querySelector("#button-capture")
.addEventListener("click", async () => {
if (document.documentElement.classList.contains("status-running")) {
await background.captureProfile();
}
});
document
.querySelector("#settings-label")
.addEventListener("click", async () => {
const { settingsOpen } = background.state;
background.adjustState({
settingsOpen: !settingsOpen,
});
renderState(background.state);
});
document.querySelector(".interval-range").addEventListener("input", async e => {
const frac = e.target.value / 100;
background.adjustState({
interval: intervalScale.fromFractionToSingleDigitValue(frac),
});
renderState(background.state);
});
document
.querySelector(".buffersize-range")
.addEventListener("input", async e => {
const frac = e.target.value / 100;
background.adjustState({
buffersize: buffersizeScale.fromFractionToSingleDigitValue(frac),
});
renderState(background.state);
});
document
.querySelector(".windowlength-range")
.addEventListener("input", async e => {
const frac = e.target.value / 100;
background.adjustState({
windowLength: windowLengthScale.fromFractionToSingleDigitValue(frac),
});
renderState(background.state);
});
window.onload = () => {
// Letting the background script know how the infiniteWindowLength is represented.
background.adjustState({
infiniteWindowLength,
});
};
renderState(background.state);
}
function renderState(state) {
const { isRunning, settingsOpen, interval, buffersize, windowLength } = state;
document.documentElement.classList.toggle("status-running", isRunning);
@ -79,11 +185,11 @@ function renderControls(state) {
document.querySelector(".windowlength-range").value =
windowLengthScale.fromValueToFraction(state.windowLength) * 100;
for (let name of features) {
for (const name of features) {
document.getElementById(featurePrefix + name).value = state[name];
}
for (let name in threadMap) {
for (const name in threadMap) {
document.getElementById(
threadPrefix + name
).checked = state.threads.includes(threadMap[name]);
@ -158,114 +264,27 @@ function calculateOverhead(state) {
);
}
function getBackground() {
return browser.runtime.getBackgroundPage();
}
document.querySelector(".button-start").addEventListener("click", async () => {
const background = await getBackground();
await background.startProfiler();
background.adjustState({ isRunning: true });
renderState(background.profilerState);
});
document.querySelector(".button-cancel").addEventListener("click", async () => {
const background = await getBackground();
await background.stopProfiler();
background.adjustState({ isRunning: false });
renderState(background.profilerState);
});
document
.querySelector("#button-capture")
.addEventListener("click", async () => {
if (document.documentElement.classList.contains("status-running")) {
const background = await getBackground();
await background.captureProfile();
window.close();
}
});
document
.querySelector("#settings-label")
.addEventListener("click", async () => {
const background = await getBackground();
background.adjustState({
settingsOpen: !background.profilerState.settingsOpen,
});
renderState(background.profilerState);
});
document.querySelector(".interval-range").addEventListener("input", async e => {
const background = await getBackground();
const frac = e.target.value / 100;
background.adjustState({
interval: intervalScale.fromFractionToSingleDigitValue(frac),
});
renderState(background.profilerState);
});
document
.querySelector(".buffersize-range")
.addEventListener("input", async e => {
const background = await getBackground();
const frac = e.target.value / 100;
background.adjustState({
buffersize: buffersizeScale.fromFractionToSingleDigitValue(frac),
});
renderState(background.profilerState);
});
document
.querySelector(".windowlength-range")
.addEventListener("input", async e => {
const background = await getBackground();
const frac = e.target.value / 100;
background.adjustState({
windowLength: windowLengthScale.fromFractionToSingleDigitValue(frac),
});
renderState(background.profilerState);
});
window.onload = async () => {
if (
!browser.geckoProfiler.supports ||
!browser.geckoProfiler.supports.WINDOWLENGTH
) {
document.body.classList.add("no-windowlength");
}
// Letting the background script know how the infiniteWindowLength is represented.
const background = await getBackground();
background.adjustState({
infiniteWindowLength,
});
};
/**
* This helper initializes and adds listeners to the features checkboxes that
* will adjust the profiler state when changed.
*/
async function setupFeatureCheckbox(name) {
const platform = await browser.runtime.getPlatformInfo();
// Java profiling is only meaningful on android.
if (name == "java") {
if (platform.os !== "android") {
if (background.platform !== "android") {
document.querySelector("#java").style.display = "none";
return;
}
}
const checkbox = document.querySelector(`#${featurePrefix}${name}`);
const background = await getBackground();
checkbox.checked = background.profilerState.features[name];
checkbox.checked = background.state.features[name];
checkbox.addEventListener("change", async e => {
const features = Object.assign({}, background.profilerState.features);
features[name] = e.target.checked;
background.adjustState({ features });
renderState(background.profilerState);
const newFeatures = Object.assign({}, background.state.features);
newFeatures[name] = e.target.checked;
background.adjustState({ features: newFeatures });
renderState(background.state);
});
}
@ -275,11 +294,10 @@ async function setupFeatureCheckbox(name) {
*/
async function setupThreadCheckbox(name) {
const checkbox = document.querySelector(`#${threadPrefix}${name}`);
const background = await getBackground();
checkbox.checked = background.profilerState.threads.includes(threadMap[name]);
checkbox.checked = background.state.threads.includes(threadMap[name]);
checkbox.addEventListener("change", async e => {
let threads = background.profilerState.threads;
let threads = background.state.threads;
if (e.target.checked) {
threads += "," + e.target.value;
} else {
@ -288,7 +306,7 @@ async function setupThreadCheckbox(name) {
.join(",");
}
background.adjustState({ threads });
renderState(background.profilerState);
renderState(background.state);
});
}
@ -309,28 +327,6 @@ function threadTextToList(threads) {
);
}
for (const name of features) {
setupFeatureCheckbox(name);
}
for (const name in threadMap) {
setupThreadCheckbox(name);
}
document
.querySelector("#perf-settings-thread-text")
.addEventListener("change", async e => {
const background = await getBackground();
background.adjustState({ threads: e.target.value });
renderState(background.profilerState);
});
document
.querySelector(".settings-apply-button")
.addEventListener("click", async () => {
(await getBackground()).restartProfiler();
});
function makeExponentialScale(rangeStart, rangeEnd) {
const startExp = Math.log(rangeStart);
const endExp = Math.log(rangeEnd);
@ -348,38 +344,35 @@ function makeExponentialScale(rangeStart, rangeEnd) {
};
}
const prettyBytes = (function(module) {
"use strict";
const UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
module.exports = num => {
if (!Number.isFinite(num)) {
throw new TypeError(
`Expected a finite number, got ${typeof num}: ${num}`
);
}
const neg = num < 0;
if (neg) {
num = -num;
}
if (num < 1) {
return (neg ? "-" : "") + num + " B";
}
const exponent = Math.min(
Math.floor(Math.log(num) / Math.log(1000)),
UNITS.length - 1
function prettyBytes(num) {
if (!Number.isFinite(num)) {
throw new TypeError(
`Expected a finite number, got ${typeof num}: ${num}`
);
const numStr = Number((num / Math.pow(1000, exponent)).toPrecision(3));
const unit = UNITS[exponent];
}
return (neg ? "-" : "") + numStr + " " + unit;
};
const neg = num < 0;
return module;
})({}).exports;
if (neg) {
num = -num;
}
getBackground().then(background => renderState(background.profilerState));
if (num < 1) {
return (neg ? "-" : "") + num + " B";
}
const exponent = Math.min(
Math.floor(Math.log(num) / Math.log(1000)),
UNITS.length - 1
);
const numStr = Number((num / Math.pow(1000, exponent)).toPrecision(3));
const unit = UNITS[exponent];
return (neg ? "-" : "") + numStr + " " + unit;
}
module.exports = {
initializePopup,
};