mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-23 02:05:42 +00:00
9c1ba92750
Differential Revision: https://phabricator.services.mozilla.com/D8869 --HG-- extra : moz-landing-system : lando
186 lines
6.3 KiB
JavaScript
186 lines
6.3 KiB
JavaScript
/* 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 Services = require("Services");
|
|
|
|
const TRANSFER_EVENT = "devtools:perf-html-transfer-profile";
|
|
const SYMBOL_TABLE_REQUEST_EVENT = "devtools:perf-html-request-symbol-table";
|
|
const SYMBOL_TABLE_RESPONSE_EVENT = "devtools:perf-html-reply-symbol-table";
|
|
const UI_BASE_URL_PREF = "devtools.performance.recording.ui-base-url";
|
|
const UI_BASE_URL_DEFAULT = "https://perf-html.io";
|
|
|
|
/**
|
|
* This file contains all of the privileged browser-specific functionality. This helps
|
|
* keep a clear separation between the privileged and non-privileged client code. It
|
|
* is also helpful in being able to mock out browser behavior for tests, without
|
|
* worrying about polluting the browser environment.
|
|
*/
|
|
|
|
/**
|
|
* Once a profile is received from the actor, it needs to be opened up in perf.html
|
|
* to be analyzed. This function opens up perf.html into a new browser tab, and injects
|
|
* the profile via a frame script.
|
|
*
|
|
* @param {object} profile - The Gecko profile.
|
|
* @param {function} getSymbolTableCallback - A callback function with the signature
|
|
* (debugName, breakpadId) => Promise<SymbolTableAsTuple>, which will be invoked
|
|
* when perf-html.io sends SYMBOL_TABLE_REQUEST_EVENT messages to us. This function
|
|
* should obtain a symbol table for the requested binary and resolve the returned
|
|
* promise with it.
|
|
*/
|
|
function receiveProfile(profile, getSymbolTableCallback) {
|
|
// Find the most recently used window, as the DevTools client could be in a variety
|
|
// of hosts.
|
|
const win = Services.wm.getMostRecentWindow("navigator:browser");
|
|
if (!win) {
|
|
throw new Error("No browser window");
|
|
}
|
|
const browser = win.gBrowser;
|
|
Services.focus.activeWindow = win;
|
|
|
|
const baseUrl = Services.prefs.getStringPref(UI_BASE_URL_PREF, UI_BASE_URL_DEFAULT);
|
|
const tab = browser.addWebTab(`${baseUrl}/from-addon`, {
|
|
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({
|
|
userContextId: browser.contentPrincipal.userContextId,
|
|
}),
|
|
});
|
|
browser.selectedTab = tab;
|
|
const mm = tab.linkedBrowser.messageManager;
|
|
mm.loadFrameScript(
|
|
"chrome://devtools/content/performance-new/frame-script.js",
|
|
false
|
|
);
|
|
mm.sendAsyncMessage(TRANSFER_EVENT, profile);
|
|
mm.addMessageListener(SYMBOL_TABLE_REQUEST_EVENT, e => {
|
|
const { debugName, breakpadId } = e.data;
|
|
getSymbolTableCallback(debugName, breakpadId).then(result => {
|
|
const [addr, index, buffer] = result;
|
|
mm.sendAsyncMessage(SYMBOL_TABLE_RESPONSE_EVENT, {
|
|
status: "success",
|
|
debugName, breakpadId, result: [addr, index, buffer],
|
|
});
|
|
}, error => {
|
|
mm.sendAsyncMessage(SYMBOL_TABLE_RESPONSE_EVENT, {
|
|
status: "error",
|
|
debugName, breakpadId,
|
|
error: `${error}`,
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Don't trust that the user has stored the correct value in preferences, or that it
|
|
* even exists. Gracefully handle malformed data or missing data. Ensure that this
|
|
* function always returns a valid array of strings.
|
|
* @param {PreferenceFront} preferenceFront
|
|
* @param {string} prefName
|
|
* @param {array of string} defaultValue
|
|
*/
|
|
async function _getArrayOfStringsPref(preferenceFront, prefName, defaultValue) {
|
|
let array;
|
|
try {
|
|
const text = await preferenceFront.getCharPref(prefName);
|
|
array = JSON.parse(text);
|
|
} catch (error) {
|
|
return defaultValue;
|
|
}
|
|
|
|
if (Array.isArray(array) && array.every(feature => typeof feature === "string")) {
|
|
return array;
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
|
|
/**
|
|
* Attempt to get a int preference value from the debuggee.
|
|
*
|
|
* @param {PreferenceFront} preferenceFront
|
|
* @param {string} prefName
|
|
* @param {number} defaultValue
|
|
*/
|
|
async function _getIntPref(preferenceFront, prefName, defaultValue) {
|
|
try {
|
|
return await preferenceFront.getIntPref(prefName);
|
|
} catch (error) {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the recording settings from the preferences. These settings are stored once
|
|
* for local debug targets, and another set of settings for remote targets. This
|
|
* is helpful for configuring for remote targets like Android phones that may require
|
|
* different features or configurations.
|
|
*
|
|
* @param {PreferenceFront} preferenceFront
|
|
* @param {object} defaultSettings See the getRecordingSettings selector for the shape
|
|
* of the object and how it gets defined.
|
|
*/
|
|
async function getRecordingPreferences(preferenceFront, defaultSettings = {}) {
|
|
const [ entries, interval, features, threads ] = await Promise.all([
|
|
_getIntPref(
|
|
preferenceFront,
|
|
`devtools.performance.recording.entries`,
|
|
defaultSettings.entries
|
|
),
|
|
_getIntPref(
|
|
preferenceFront,
|
|
`devtools.performance.recording.interval`,
|
|
defaultSettings.interval
|
|
),
|
|
_getArrayOfStringsPref(
|
|
preferenceFront,
|
|
`devtools.performance.recording.features`,
|
|
defaultSettings.features
|
|
),
|
|
_getArrayOfStringsPref(
|
|
preferenceFront,
|
|
`devtools.performance.recording.threads`,
|
|
defaultSettings.threads
|
|
),
|
|
]);
|
|
|
|
// The pref stores the value in usec.
|
|
const newInterval = interval / 1000;
|
|
return { entries, interval: newInterval, features, threads };
|
|
}
|
|
|
|
/**
|
|
* Take the recording settings, as defined by the getRecordingSettings selector, and
|
|
* persist them to preferences.
|
|
*
|
|
* @param {PreferenceFront} preferenceFront
|
|
* @param {object} defaultSettings See the getRecordingSettings selector for the shape
|
|
* of the object and how it gets defined.
|
|
*/
|
|
async function setRecordingPreferences(preferenceFront, settings) {
|
|
await Promise.all([
|
|
preferenceFront.setIntPref(
|
|
`devtools.performance.recording.entries`,
|
|
settings.entries
|
|
),
|
|
preferenceFront.setIntPref(
|
|
`devtools.performance.recording.interval`,
|
|
// The pref stores the value in usec.
|
|
settings.interval * 1000
|
|
),
|
|
preferenceFront.setCharPref(
|
|
`devtools.performance.recording.features`,
|
|
JSON.stringify(settings.features)
|
|
),
|
|
preferenceFront.setCharPref(
|
|
`devtools.performance.recording.threads`,
|
|
JSON.stringify(settings.threads)
|
|
),
|
|
]);
|
|
}
|
|
|
|
module.exports = {
|
|
receiveProfile,
|
|
getRecordingPreferences,
|
|
setRecordingPreferences,
|
|
};
|