mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 08:12:05 +00:00
Bug 828038 - Change profile recording UI and behavior; r=dcamp
This commit is contained in:
parent
ec75a4559a
commit
1706e0cec0
@ -2,10 +2,10 @@
|
||||
* 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/. */
|
||||
|
||||
|
||||
this.EXPORTED_SYMBOLS = [ ];
|
||||
this.EXPORTED_SYMBOLS = [];
|
||||
|
||||
const Cu = Components.utils;
|
||||
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
|
||||
Cu.import("resource:///modules/devtools/BuiltinCommands.jsm");
|
||||
Cu.import("resource:///modules/devtools/CmdDebugger.jsm");
|
||||
@ -14,4 +14,5 @@ Cu.import("resource:///modules/devtools/CmdInspect.jsm");
|
||||
Cu.import("resource:///modules/devtools/CmdResize.jsm");
|
||||
Cu.import("resource:///modules/devtools/CmdTilt.jsm");
|
||||
Cu.import("resource:///modules/devtools/CmdScratchpad.jsm");
|
||||
Cu.import("resource:///modules/devtools/cmd-profiler.jsm");
|
||||
|
||||
require("devtools/profiler/commands.js");
|
||||
|
@ -13,7 +13,8 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
Cu.import("resource:///modules/devtools/ProfilerController.jsm");
|
||||
|
||||
var ProfilerController = devtools.require("devtools/profiler/controller");
|
||||
|
||||
const FORBIDDEN_IDS = new Set(["toolbox", ""]);
|
||||
const MAX_ORDINAL = 99;
|
||||
|
@ -25,7 +25,7 @@ loader.lazyGetter(this, "InspectorPanel", function() require("devtools/inspector
|
||||
loader.lazyImporter(this, "WebConsolePanel", "resource:///modules/WebConsolePanel.jsm");
|
||||
loader.lazyImporter(this, "DebuggerPanel", "resource:///modules/devtools/DebuggerPanel.jsm");
|
||||
loader.lazyImporter(this, "StyleEditorPanel", "resource:///modules/devtools/StyleEditorPanel.jsm");
|
||||
loader.lazyImporter(this, "ProfilerPanel", "resource:///modules/devtools/ProfilerPanel.jsm");
|
||||
loader.lazyGetter(this, "ProfilerPanel", function() require("devtools/profiler/panel"));
|
||||
loader.lazyImporter(this, "NetMonitorPanel", "resource:///modules/devtools/NetMonitorPanel.jsm");
|
||||
|
||||
// Strings
|
||||
|
@ -12,4 +12,4 @@ include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
libs::
|
||||
$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
|
||||
$(NSINSTALL) $(srcdir)/*.js $(FINAL_TARGET)/modules/devtools/profiler
|
||||
|
@ -1,43 +0,0 @@
|
||||
/* 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 Cu = Components.utils;
|
||||
const ProfilerProps = "chrome://browser/locale/devtools/profiler.properties";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["L10N"];
|
||||
|
||||
/**
|
||||
* Localization helper methods.
|
||||
*/
|
||||
let L10N = {
|
||||
/**
|
||||
* Returns a simple localized string.
|
||||
*
|
||||
* @param string name
|
||||
* @return string
|
||||
*/
|
||||
getStr: function L10N_getStr(name) {
|
||||
return this.stringBundle.GetStringFromName(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns formatted localized string.
|
||||
*
|
||||
* @param string name
|
||||
* @param array params
|
||||
* @return string
|
||||
*/
|
||||
getFormatStr: function L10N_getFormatStr(name, params) {
|
||||
return this.stringBundle.formatStringFromName(name, params, params.length);
|
||||
}
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function () {
|
||||
return Services.strings.createBundle(ProfilerProps);
|
||||
});
|
@ -1,716 +0,0 @@
|
||||
/* 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 Cu = Components.utils;
|
||||
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
Cu.import("resource:///modules/devtools/ProfilerController.jsm");
|
||||
Cu.import("resource:///modules/devtools/ProfilerHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ProfilerPanel"];
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
|
||||
"resource://gre/modules/devtools/dbg-server.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
const PROFILE_IDLE = 0;
|
||||
const PROFILE_RUNNING = 1;
|
||||
const PROFILE_COMPLETED = 2;
|
||||
|
||||
/**
|
||||
* An instance of a profile UI. Profile UI consists of
|
||||
* an iframe with Cleopatra loaded in it and some
|
||||
* surrounding meta-data (such as uids).
|
||||
*
|
||||
* Its main function is to talk to the Cleopatra instance
|
||||
* inside of the iframe.
|
||||
*
|
||||
* ProfileUI is also an event emitter. It emits the following events:
|
||||
* - ready, when Cleopatra is done loading (you can also check the isReady
|
||||
* property to see if a particular instance has been loaded yet.
|
||||
* - disabled, when Cleopatra gets disabled. Happens when another ProfileUI
|
||||
* instance starts the profiler.
|
||||
* - enabled, when Cleopatra gets enabled. Happens when another ProfileUI
|
||||
* instance stops the profiler.
|
||||
*
|
||||
* @param number uid
|
||||
* Unique ID for this profile.
|
||||
* @param ProfilerPanel panel
|
||||
* A reference to the container panel.
|
||||
*/
|
||||
function ProfileUI(uid, name, panel) {
|
||||
let doc = panel.document;
|
||||
let win = panel.window;
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.isReady = false;
|
||||
this.isStarted = false;
|
||||
this.isFinished = false;
|
||||
|
||||
this.messages = [];
|
||||
this.panel = panel;
|
||||
this.uid = uid;
|
||||
this.name = name;
|
||||
|
||||
this.iframe = doc.createElement("iframe");
|
||||
this.iframe.setAttribute("flex", "1");
|
||||
this.iframe.setAttribute("id", "profiler-cleo-" + uid);
|
||||
this.iframe.setAttribute("src", "cleopatra.html?" + uid);
|
||||
this.iframe.setAttribute("hidden", "true");
|
||||
|
||||
// Append our iframe and subscribe to postMessage events.
|
||||
// They'll tell us when the underlying page is done loading
|
||||
// or when user clicks on start/stop buttons.
|
||||
|
||||
doc.getElementById("profiler-report").appendChild(this.iframe);
|
||||
win.addEventListener("message", function (event) {
|
||||
if (parseInt(event.data.uid, 10) !== parseInt(this.uid, 10)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.data.status) {
|
||||
case "loaded":
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
break;
|
||||
case "start":
|
||||
this.start();
|
||||
break;
|
||||
case "stop":
|
||||
this.stop();
|
||||
break;
|
||||
case "disabled":
|
||||
this.emit("disabled");
|
||||
break;
|
||||
case "enabled":
|
||||
this.emit("enabled");
|
||||
break;
|
||||
case "displaysource":
|
||||
this.panel.displaySource(event.data.data);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
ProfileUI.prototype = {
|
||||
/**
|
||||
* Returns a contentWindow of the iframe pointing to Cleopatra
|
||||
* if it exists and can be accessed. Otherwise returns null.
|
||||
*/
|
||||
get contentWindow() {
|
||||
if (!this.iframe) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return this.iframe.contentWindow;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
show: function PUI_show() {
|
||||
this.iframe.removeAttribute("hidden");
|
||||
},
|
||||
|
||||
hide: function PUI_hide() {
|
||||
this.iframe.setAttribute("hidden", true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send raw profiling data to Cleopatra for parsing.
|
||||
*
|
||||
* @param object data
|
||||
* Raw profiling data from the SPS Profiler.
|
||||
* @param function onParsed
|
||||
* A callback to be called when Cleopatra finishes
|
||||
* parsing and displaying results.
|
||||
*
|
||||
*/
|
||||
parse: function PUI_parse(data, onParsed) {
|
||||
if (!this.isReady) {
|
||||
return void this.on("ready", this.parse.bind(this, data, onParsed));
|
||||
}
|
||||
|
||||
this.message({ task: "receiveProfileData", rawProfile: data }).then(() => {
|
||||
let poll = () => {
|
||||
let wait = this.panel.window.setTimeout.bind(null, poll, 100);
|
||||
let trail = this.contentWindow.gBreadcrumbTrail;
|
||||
|
||||
if (!trail) {
|
||||
return wait();
|
||||
}
|
||||
|
||||
if (!trail._breadcrumbs || !trail._breadcrumbs.length) {
|
||||
return wait();
|
||||
}
|
||||
|
||||
onParsed();
|
||||
};
|
||||
|
||||
poll();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Start profiling and, once started, notify the underlying page
|
||||
* so that it could update the UI. Also, once started, we add a
|
||||
* star to the profile name to indicate which profile is currently
|
||||
* running.
|
||||
*
|
||||
* @param function startFn
|
||||
* A function to use instead of the default
|
||||
* this.panel.startProfiling. Useful when you
|
||||
* need mark panel as started after the profiler
|
||||
* has been started elsewhere. It must take two
|
||||
* params and call the second one.
|
||||
*/
|
||||
start: function PUI_start(startFn) {
|
||||
if (this.isStarted || this.isFinished) {
|
||||
return;
|
||||
}
|
||||
|
||||
startFn = startFn || this.panel.startProfiling.bind(this.panel);
|
||||
startFn(this.name, () => {
|
||||
this.isStarted = true;
|
||||
this.panel.sidebar.setProfileState(this, PROFILE_RUNNING);
|
||||
this.panel.broadcast(this.uid, {task: "onStarted"}); // Do we really need this?
|
||||
this.emit("started");
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop profiling and, once stopped, notify the underlying page so
|
||||
* that it could update the UI and remove a star from the profile
|
||||
* name.
|
||||
*
|
||||
* @param function stopFn
|
||||
* A function to use instead of the default
|
||||
* this.panel.stopProfiling. Useful when you
|
||||
* need mark panel as stopped after the profiler
|
||||
* has been stopped elsewhere. It must take two
|
||||
* params and call the second one.
|
||||
*/
|
||||
stop: function PUI_stop(stopFn) {
|
||||
if (!this.isStarted || this.isFinished) {
|
||||
return;
|
||||
}
|
||||
|
||||
stopFn = stopFn || this.panel.stopProfiling.bind(this.panel);
|
||||
stopFn(this.name, () => {
|
||||
this.isStarted = false;
|
||||
this.isFinished = true;
|
||||
this.panel.sidebar.setProfileState(this, PROFILE_COMPLETED);
|
||||
this.panel.broadcast(this.uid, {task: "onStopped"});
|
||||
this.emit("stopped");
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a message to Cleopatra instance. If a message cannot be
|
||||
* sent, this method queues it for later.
|
||||
*
|
||||
* @param object data JSON data to send (must be serializable)
|
||||
* @return promise
|
||||
*/
|
||||
message: function PIU_message(data) {
|
||||
let deferred = Promise.defer();
|
||||
let win = this.contentWindow;
|
||||
data = JSON.stringify(data);
|
||||
|
||||
if (win) {
|
||||
win.postMessage(data, "*");
|
||||
deferred.resolve();
|
||||
} else {
|
||||
this.messages.push({ data: data, onSuccess: () => deferred.resolve() });
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send all queued messages (see this.message for more info)
|
||||
*/
|
||||
flushMessages: function PIU_flushMessages() {
|
||||
if (!this.contentWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
let msg;
|
||||
while (msg = this.messages.shift()) {
|
||||
this.contentWindow.postMessage(msg.data, "*");
|
||||
msg.onSuccess();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys the ProfileUI instance.
|
||||
*/
|
||||
destroy: function PUI_destroy() {
|
||||
this.isReady = null
|
||||
this.panel = null;
|
||||
this.uid = null;
|
||||
this.iframe = null;
|
||||
this.messages = null;
|
||||
}
|
||||
};
|
||||
|
||||
function SidebarView(el) {
|
||||
EventEmitter.decorate(this);
|
||||
this.widget = new SideMenuWidget(el);
|
||||
}
|
||||
|
||||
SidebarView.prototype = Heritage.extend(WidgetMethods, {
|
||||
getItemByProfile: function (profile) {
|
||||
return this.getItemForPredicate(item => item.attachment.uid === profile.uid);
|
||||
},
|
||||
|
||||
setProfileState: function (profile, state) {
|
||||
let item = this.getItemByProfile(profile);
|
||||
let label = item.target.querySelector(".profiler-sidebar-item > span");
|
||||
|
||||
switch (state) {
|
||||
case PROFILE_IDLE:
|
||||
label.textContent = L10N.getStr("profiler.stateIdle");
|
||||
break;
|
||||
case PROFILE_RUNNING:
|
||||
label.textContent = L10N.getStr("profiler.stateRunning");
|
||||
break;
|
||||
case PROFILE_COMPLETED:
|
||||
label.textContent = L10N.getStr("profiler.stateCompleted");
|
||||
break;
|
||||
default: // Wrong state, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
item.attachment.state = state;
|
||||
this.emit("stateChanged", item);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Profiler panel. It is responsible for creating and managing
|
||||
* different profile instances (see ProfileUI).
|
||||
*
|
||||
* ProfilerPanel is an event emitter. It can emit the following
|
||||
* events:
|
||||
*
|
||||
* - ready: after the panel is done loading everything,
|
||||
* including the default profile instance.
|
||||
* - started: after the panel successfuly starts our SPS
|
||||
* profiler.
|
||||
* - stopped: after the panel successfuly stops our SPS
|
||||
* profiler and is ready to hand over profiling
|
||||
* data
|
||||
* - parsed: after Cleopatra finishes parsing profiling
|
||||
* data.
|
||||
* - destroyed: after the panel cleans up after itself and
|
||||
* is ready to be destroyed.
|
||||
*
|
||||
* The following events are used mainly by tests to prevent
|
||||
* accidential oranges:
|
||||
*
|
||||
* - profileCreated: after a new profile is created.
|
||||
* - profileSwitched: after user switches to a different
|
||||
* profile.
|
||||
*/
|
||||
function ProfilerPanel(frame, toolbox) {
|
||||
this.isReady = false;
|
||||
this.window = frame.window;
|
||||
this.document = frame.document;
|
||||
this.target = toolbox.target;
|
||||
|
||||
this.profiles = new Map();
|
||||
this._uid = 0;
|
||||
this._msgQueue = {};
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
ProfilerPanel.prototype = {
|
||||
isReady: null,
|
||||
window: null,
|
||||
document: null,
|
||||
target: null,
|
||||
controller: null,
|
||||
profiles: null,
|
||||
sidebar: null,
|
||||
|
||||
_uid: null,
|
||||
_activeUid: null,
|
||||
_runningUid: null,
|
||||
_browserWin: null,
|
||||
_msgQueue: null,
|
||||
|
||||
get activeProfile() {
|
||||
return this.profiles.get(this._activeUid);
|
||||
},
|
||||
|
||||
set activeProfile(profile) {
|
||||
if (this._activeUid === profile.uid)
|
||||
return;
|
||||
|
||||
if (this.activeProfile)
|
||||
this.activeProfile.hide();
|
||||
|
||||
this._activeUid = profile.uid;
|
||||
profile.show();
|
||||
},
|
||||
|
||||
get browserWindow() {
|
||||
if (this._browserWin) {
|
||||
return this._browserWin;
|
||||
}
|
||||
|
||||
let win = this.window.top;
|
||||
let type = win.document.documentElement.getAttribute("windowtype");
|
||||
|
||||
if (type !== "navigator:browser") {
|
||||
win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
}
|
||||
|
||||
return this._browserWin = win;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open a debug connection and, on success, switch to the newly created
|
||||
* profile.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
open: function PP_open() {
|
||||
// Local profiling needs to make the target remote.
|
||||
let target = this.target;
|
||||
let promise = !target.isRemote ? target.makeRemote() : Promise.resolve(target);
|
||||
|
||||
return promise
|
||||
.then((target) => {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
this.controller = new ProfilerController(this.target);
|
||||
this.sidebar = new SidebarView(this.document.querySelector("#profiles-list"));
|
||||
this.sidebar.widget.addEventListener("select", (ev) => {
|
||||
if (!ev.detail)
|
||||
return;
|
||||
|
||||
let profile = this.profiles.get(ev.detail.attachment.uid);
|
||||
this.activeProfile = profile;
|
||||
|
||||
if (profile.isReady) {
|
||||
profile.flushMessages();
|
||||
return void this.emit("profileSwitched", profile.uid);
|
||||
}
|
||||
|
||||
profile.once("ready", () => {
|
||||
profile.flushMessages();
|
||||
this.emit("profileSwitched", profile.uid);
|
||||
});
|
||||
});
|
||||
|
||||
this.controller.connect(() => {
|
||||
let create = this.document.getElementById("profiler-create");
|
||||
create.addEventListener("click", () => this.createProfile(), false);
|
||||
create.removeAttribute("disabled");
|
||||
|
||||
let profile = this.createProfile();
|
||||
let onSwitch = (_, uid) => {
|
||||
if (profile.uid !== uid)
|
||||
return;
|
||||
|
||||
this.off("profileSwitched", onSwitch);
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
|
||||
deferred.resolve(this);
|
||||
};
|
||||
|
||||
this.on("profileSwitched", onSwitch);
|
||||
this.sidebar.selectedItem = this.sidebar.getItemByProfile(profile);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
})
|
||||
.then(null, (reason) =>
|
||||
Cu.reportError("ProfilePanel open failed: " + reason.message));
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new profile instance (see ProfileUI) and
|
||||
* adds an appropriate item to the sidebar. Note that
|
||||
* this method doesn't automatically switch user to
|
||||
* the newly created profile, they have do to switch
|
||||
* explicitly.
|
||||
*
|
||||
* @param string name
|
||||
* (optional) name of the new profile
|
||||
*
|
||||
* @return ProfilerPanel
|
||||
*/
|
||||
createProfile: function PP_createProfile(name) {
|
||||
if (name && this.getProfileByName(name)) {
|
||||
return this.getProfileByName(name);
|
||||
}
|
||||
|
||||
let uid = ++this._uid;
|
||||
|
||||
// If profile is anonymous, increase its UID until we get
|
||||
// to the unused name. This way if someone manually creates
|
||||
// a profile named say 'Profile 2' we won't create a dup
|
||||
// with the same name. We will just skip over uid 2.
|
||||
|
||||
if (!name) {
|
||||
name = L10N.getFormatStr("profiler.profileName", [uid]);
|
||||
while (this.getProfileByName(name)) {
|
||||
uid = ++this._uid;
|
||||
name = L10N.getFormatStr("profiler.profileName", [uid]);
|
||||
}
|
||||
}
|
||||
|
||||
let box = this.document.createElement("vbox");
|
||||
box.className = "profiler-sidebar-item";
|
||||
box.id = "profile-" + uid;
|
||||
let h3 = this.document.createElement("h3");
|
||||
h3.textContent = name;
|
||||
let span = this.document.createElement("span");
|
||||
span.textContent = L10N.getStr("profiler.stateIdle");
|
||||
box.appendChild(h3);
|
||||
box.appendChild(span);
|
||||
|
||||
this.sidebar.push([box], { attachment: { uid: uid, name: name, state: PROFILE_IDLE } });
|
||||
|
||||
let profile = new ProfileUI(uid, name, this);
|
||||
this.profiles.set(uid, profile);
|
||||
|
||||
this.emit("profileCreated", uid);
|
||||
return profile;
|
||||
},
|
||||
|
||||
/**
|
||||
* Start collecting profile data.
|
||||
*
|
||||
* @param function onStart
|
||||
* A function to call once we get the message
|
||||
* that profiling had been successfuly started.
|
||||
*/
|
||||
startProfiling: function PP_startProfiling(name, onStart) {
|
||||
this.controller.start(name, (err) => {
|
||||
if (err) {
|
||||
return void Cu.reportError("ProfilerController.start: " + err.message);
|
||||
}
|
||||
|
||||
onStart();
|
||||
this.emit("started");
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop collecting profile data and send it to Cleopatra
|
||||
* for parsing.
|
||||
*
|
||||
* @param function onStop
|
||||
* A function to call once we get the message
|
||||
* that profiling had been successfuly stopped.
|
||||
*/
|
||||
stopProfiling: function PP_stopProfiling(name, onStop) {
|
||||
this.controller.isActive(function (err, isActive) {
|
||||
if (err) {
|
||||
Cu.reportError("ProfilerController.isActive: " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.controller.stop(name, function (err, data) {
|
||||
if (err) {
|
||||
Cu.reportError("ProfilerController.stop: " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeProfile.data = data;
|
||||
this.activeProfile.parse(data, function onParsed() {
|
||||
this.emit("parsed");
|
||||
}.bind(this));
|
||||
|
||||
onStop();
|
||||
this.emit("stopped", data);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Lookup an individual profile by its name.
|
||||
*
|
||||
* @param string name name of the profile
|
||||
* @return profile object or null
|
||||
*/
|
||||
getProfileByName: function PP_getProfileByName(name) {
|
||||
if (!this.profiles) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let [ uid, profile ] of this.profiles) {
|
||||
if (profile.name === name) {
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Lookup an individual profile by its UID.
|
||||
*
|
||||
* @param number uid UID of the profile
|
||||
* @return profile object or null
|
||||
*/
|
||||
getProfileByUID: function PP_getProfileByUID(uid) {
|
||||
if (!this.profiles) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.profiles.get(uid) || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterates over each available profile and calls
|
||||
* a callback with it as a parameter.
|
||||
*
|
||||
* @param function cb a callback to call
|
||||
*/
|
||||
eachProfile: function PP_eachProfile(cb) {
|
||||
let uid = this._uid;
|
||||
|
||||
if (!this.profiles) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (uid >= 0) {
|
||||
if (this.profiles.has(uid)) {
|
||||
cb(this.profiles.get(uid));
|
||||
}
|
||||
|
||||
uid -= 1;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Broadcast messages to all Cleopatra instances.
|
||||
*
|
||||
* @param number target
|
||||
* UID of the recepient profile. All profiles will receive the message
|
||||
* but the profile specified by 'target' will have a special property,
|
||||
* isCurrent, set to true.
|
||||
* @param object data
|
||||
* An object with a property 'task' that will be sent over to Cleopatra.
|
||||
*/
|
||||
broadcast: function PP_broadcast(target, data) {
|
||||
if (!this.profiles) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.task === "onStarted") {
|
||||
this._runningUid = target;
|
||||
} else {
|
||||
this._runningUid = null;
|
||||
}
|
||||
|
||||
this.eachProfile((profile) => {
|
||||
profile.message({
|
||||
uid: target,
|
||||
isCurrent: target === profile.uid,
|
||||
task: data.task
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Open file specified in data in either a debugger or view-source.
|
||||
*
|
||||
* @param object data
|
||||
* An object describing the file. It must have three properties:
|
||||
* - uri
|
||||
* - line
|
||||
* - isChrome (chrome files are opened via view-source)
|
||||
*/
|
||||
displaySource: function PP_displaySource(data, onOpen=function() {}) {
|
||||
let win = this.window;
|
||||
let panelWin, timeout;
|
||||
|
||||
function onSourceShown(event) {
|
||||
if (event.detail.url !== data.uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
panelWin.removeEventListener("Debugger:SourceShown", onSourceShown, false);
|
||||
panelWin.editor.setCaretPosition(data.line - 1);
|
||||
onOpen();
|
||||
}
|
||||
|
||||
if (data.isChrome) {
|
||||
return void this.browserWindow.gViewSourceUtils.
|
||||
viewSource(data.uri, null, this.document, data.line);
|
||||
}
|
||||
|
||||
gDevTools.showToolbox(this.target, "jsdebugger").then(function (toolbox) {
|
||||
let dbg = toolbox.getCurrentPanel();
|
||||
panelWin = dbg.panelWin;
|
||||
|
||||
let view = dbg.panelWin.DebuggerView;
|
||||
if (view.Sources.selectedValue === data.uri) {
|
||||
view.editor.setCaretPosition(data.line - 1);
|
||||
onOpen();
|
||||
return;
|
||||
}
|
||||
|
||||
panelWin.addEventListener("Debugger:SourceShown", onSourceShown, false);
|
||||
panelWin.DebuggerView.Sources.preferredSource = data.uri;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup.
|
||||
*/
|
||||
destroy: function PP_destroy() {
|
||||
if (this.profiles) {
|
||||
let uid = this._uid;
|
||||
|
||||
while (uid >= 0) {
|
||||
if (this.profiles.has(uid)) {
|
||||
this.profiles.get(uid).destroy();
|
||||
this.profiles.delete(uid);
|
||||
}
|
||||
uid -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.controller) {
|
||||
this.controller.destroy();
|
||||
}
|
||||
|
||||
this.isReady = null;
|
||||
this.window = null;
|
||||
this.document = null;
|
||||
this.target = null;
|
||||
this.controller = null;
|
||||
this.profiles = null;
|
||||
this._uid = null;
|
||||
this._activeUid = null;
|
||||
|
||||
this.emit("destroyed");
|
||||
}
|
||||
};
|
162
browser/devtools/profiler/cleopatra.js
Normal file
162
browser/devtools/profiler/cleopatra.js
Normal file
@ -0,0 +1,162 @@
|
||||
/* 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";
|
||||
|
||||
let { defer } = require("sdk/core/promise");
|
||||
let EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const { PROFILE_IDLE, PROFILE_COMPLETED, PROFILE_RUNNING } = require("devtools/profiler/consts");
|
||||
|
||||
/**
|
||||
* An implementation of a profile visualization that uses Cleopatra.
|
||||
* It consists of an iframe with Cleopatra loaded in it and some
|
||||
* surrounding meta-data (such as UIDs).
|
||||
*
|
||||
* Cleopatra is also an event emitter. It emits the following events:
|
||||
* - ready, when Cleopatra is done loading (you can also check the isReady
|
||||
* property to see if a particular instance has been loaded yet.
|
||||
*
|
||||
* @param number uid
|
||||
* Unique ID for this profile.
|
||||
* @param ProfilerPanel panel
|
||||
* A reference to the container panel.
|
||||
*/
|
||||
function Cleopatra(uid, name, panel) {
|
||||
let doc = panel.document;
|
||||
let win = panel.window;
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.isReady = false;
|
||||
this.isStarted = false;
|
||||
this.isFinished = false;
|
||||
|
||||
this.panel = panel;
|
||||
this.uid = uid;
|
||||
this.name = name;
|
||||
|
||||
this.iframe = doc.createElement("iframe");
|
||||
this.iframe.setAttribute("flex", "1");
|
||||
this.iframe.setAttribute("id", "profiler-cleo-" + uid);
|
||||
this.iframe.setAttribute("src", "cleopatra.html?" + uid);
|
||||
this.iframe.setAttribute("hidden", "true");
|
||||
|
||||
// Append our iframe and subscribe to postMessage events.
|
||||
// They'll tell us when the underlying page is done loading
|
||||
// or when user clicks on start/stop buttons.
|
||||
|
||||
doc.getElementById("profiler-report").appendChild(this.iframe);
|
||||
win.addEventListener("message", function (event) {
|
||||
if (parseInt(event.data.uid, 10) !== parseInt(this.uid, 10)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.data.status) {
|
||||
case "loaded":
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
break;
|
||||
case "displaysource":
|
||||
this.panel.displaySource(event.data.data);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
Cleopatra.prototype = {
|
||||
/**
|
||||
* Returns a contentWindow of the iframe pointing to Cleopatra
|
||||
* if it exists and can be accessed. Otherwise returns null.
|
||||
*/
|
||||
get contentWindow() {
|
||||
if (!this.iframe) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return this.iframe.contentWindow;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
show: function () {
|
||||
this.iframe.removeAttribute("hidden");
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
this.iframe.setAttribute("hidden", true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send raw profiling data to Cleopatra for parsing.
|
||||
*
|
||||
* @param object data
|
||||
* Raw profiling data from the SPS Profiler.
|
||||
* @param function onParsed
|
||||
* A callback to be called when Cleopatra finishes
|
||||
* parsing and displaying results.
|
||||
*
|
||||
*/
|
||||
parse: function (data, onParsed) {
|
||||
if (!this.isReady) {
|
||||
return void this.on("ready", this.parse.bind(this, data, onParsed));
|
||||
}
|
||||
|
||||
this.message({ task: "receiveProfileData", rawProfile: data }).then(() => {
|
||||
let poll = () => {
|
||||
let wait = this.panel.window.setTimeout.bind(null, poll, 100);
|
||||
let trail = this.contentWindow.gBreadcrumbTrail;
|
||||
|
||||
if (!trail) {
|
||||
return wait();
|
||||
}
|
||||
|
||||
if (!trail._breadcrumbs || !trail._breadcrumbs.length) {
|
||||
return wait();
|
||||
}
|
||||
|
||||
onParsed();
|
||||
};
|
||||
|
||||
poll();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a message to Cleopatra instance. If a message cannot be
|
||||
* sent, this method queues it for later.
|
||||
*
|
||||
* @param object data JSON data to send (must be serializable)
|
||||
* @return promise
|
||||
*/
|
||||
message: function (data) {
|
||||
let deferred = defer();
|
||||
data = JSON.stringify(data);
|
||||
|
||||
let send = () => {
|
||||
if (!this.contentWindow)
|
||||
setTimeout(send, 50);
|
||||
|
||||
this.contentWindow.postMessage(data, "*");
|
||||
deferred.resolve();
|
||||
};
|
||||
|
||||
send();
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys the ProfileUI instance.
|
||||
*/
|
||||
destroy: function () {
|
||||
this.isReady = null;
|
||||
this.panel = null;
|
||||
this.uid = null;
|
||||
this.iframe = null;
|
||||
this.messages = null;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Cleopatra;
|
@ -2,19 +2,7 @@
|
||||
* 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/. */
|
||||
|
||||
#mainarea > .controlPane {
|
||||
font-size: 120%;
|
||||
padding-top: 75px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#stopWrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#profilerMessage {
|
||||
color: #999;
|
||||
display: none;
|
||||
#mainarea {
|
||||
}
|
||||
|
||||
/* De-emphasize chrome functions */
|
||||
|
@ -11,10 +11,6 @@ var gInstanceUID;
|
||||
* @param string status
|
||||
* Status to send to the parent page:
|
||||
* - loaded, when page is loaded.
|
||||
* - start, when user wants to start profiling.
|
||||
* - stop, when user wants to stop profiling.
|
||||
* - disabled, when the profiler was disabled
|
||||
* - enabled, when the profiler was enabled
|
||||
* - displaysource, when user wants to display source
|
||||
* @param object data (optional)
|
||||
* Additional data to send to the parent page.
|
||||
@ -109,22 +105,10 @@ function initUI() {
|
||||
notifyParent("stop");
|
||||
}, false);
|
||||
|
||||
var controlPane = document.createElement("div");
|
||||
var startProfiling = gStrings.getFormatStr("profiler.startProfiling",
|
||||
["<span class='btn'></span>"]);
|
||||
var stopProfiling = gStrings.getFormatStr("profiler.stopProfiling",
|
||||
["<span class='btn'></span>"]);
|
||||
|
||||
controlPane.className = "controlPane";
|
||||
controlPane.innerHTML =
|
||||
"<p id='startWrapper'>" + startProfiling + "</p>" +
|
||||
"<p id='stopWrapper'>" + stopProfiling + "</p>" +
|
||||
"<p id='profilerMessage'></p>";
|
||||
|
||||
controlPane.querySelector("#startWrapper > span.btn").appendChild(startButton);
|
||||
controlPane.querySelector("#stopWrapper > span.btn").appendChild(stopButton);
|
||||
|
||||
gMainArea.appendChild(controlPane);
|
||||
var message = document.createElement("div");
|
||||
message.className = "message";
|
||||
message.innerHTML = "To start profiling click the button above.";
|
||||
gMainArea.appendChild(message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,9 @@
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource:///modules/devtools/ProfilerHelpers.jsm");
|
||||
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
const { L10N_BUNDLE } = require("devtools/profiler/consts");
|
||||
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
var L10N = new ViewHelpers.L10N(L10N_BUNDLE);
|
||||
|
||||
/**
|
||||
* Shortcuts for the L10N helper functions. Used in Cleopatra.
|
||||
|
@ -2,20 +2,15 @@
|
||||
* 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/. */
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
this.EXPORTED_SYMBOLS = [];
|
||||
const { Cu } = require("chrome");
|
||||
module.exports = [];
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/gcli.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Require.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
|
||||
"resource:///modules/devtools/gDevTools.jsm");
|
||||
loader.lazyGetter(this, "gDevTools",
|
||||
() => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
var Promise = require('util/promise');
|
||||
var Promise = require("sdk/core/promise");
|
||||
|
||||
/*
|
||||
* 'profiler' command. Doesn't do anything.
|
||||
@ -64,39 +59,17 @@ gcli.addCommand({
|
||||
name: "profiler start",
|
||||
description: gcli.lookup("profilerStartDesc"),
|
||||
returnType: "string",
|
||||
|
||||
params: [
|
||||
{
|
||||
name: "name",
|
||||
type: "string",
|
||||
manual: gcli.lookup("profilerStartManual")
|
||||
}
|
||||
],
|
||||
params: [],
|
||||
|
||||
exec: function (args, context) {
|
||||
function start() {
|
||||
let name = args.name;
|
||||
let panel = getPanel(context, "jsprofiler");
|
||||
let profile = panel.getProfileByName(name) || panel.createProfile(name);
|
||||
|
||||
if (profile.isStarted) {
|
||||
throw gcli.lookup("profilerAlreadyStarted");
|
||||
}
|
||||
if (panel.recordingProfile)
|
||||
throw gcli.lookup("profilerAlreadyStarted2");
|
||||
|
||||
if (profile.isFinished) {
|
||||
throw gcli.lookup("profilerAlreadyFinished");
|
||||
}
|
||||
|
||||
let item = panel.sidebar.getItemByProfile(profile);
|
||||
|
||||
if (panel.sidebar.selectedItem === item) {
|
||||
profile.start();
|
||||
} else {
|
||||
panel.on("profileSwitched", () => profile.start());
|
||||
panel.sidebar.selectedItem = item;
|
||||
}
|
||||
|
||||
return gcli.lookup("profilerStarting2");
|
||||
panel.toggleRecording();
|
||||
return gcli.lookup("profilerStarted");
|
||||
}
|
||||
|
||||
return gDevTools.showToolbox(context.environment.target, "jsprofiler")
|
||||
@ -111,42 +84,16 @@ gcli.addCommand({
|
||||
name: "profiler stop",
|
||||
description: gcli.lookup("profilerStopDesc"),
|
||||
returnType: "string",
|
||||
|
||||
params: [
|
||||
{
|
||||
name: "name",
|
||||
type: "string",
|
||||
manual: gcli.lookup("profilerStopManual")
|
||||
}
|
||||
],
|
||||
params: [],
|
||||
|
||||
exec: function (args, context) {
|
||||
function stop() {
|
||||
let panel = getPanel(context, "jsprofiler");
|
||||
let profile = panel.getProfileByName(args.name);
|
||||
|
||||
if (!profile) {
|
||||
throw gcli.lookup("profilerNotFound");
|
||||
}
|
||||
if (!panel.recordingProfile)
|
||||
throw gcli.lookup("profilerNotStarted3");
|
||||
|
||||
if (profile.isFinished) {
|
||||
throw gcli.lookup("profilerAlreadyFinished");
|
||||
}
|
||||
|
||||
if (!profile.isStarted) {
|
||||
throw gcli.lookup("profilerNotStarted2");
|
||||
}
|
||||
|
||||
let item = panel.sidebar.getItemByProfile(profile);
|
||||
|
||||
if (panel.sidebar.selectedItem === item) {
|
||||
profile.stop();
|
||||
} else {
|
||||
panel.on("profileSwitched", () => profile.stop());
|
||||
panel.sidebar.selectedItem = item;
|
||||
}
|
||||
|
||||
return gcli.lookup("profilerStopping2");
|
||||
panel.toggleRecording();
|
||||
}
|
||||
|
||||
return gDevTools.showToolbox(context.environment.target, "jsprofiler")
|
13
browser/devtools/profiler/consts.js
Normal file
13
browser/devtools/profiler/consts.js
Normal file
@ -0,0 +1,13 @@
|
||||
/* 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";
|
||||
|
||||
module.exports = {
|
||||
L10N_BUNDLE: "chrome://browser/locale/devtools/profiler.properties",
|
||||
PROFILER_ENABLED: "devtools.profiler.enabled",
|
||||
PROFILE_IDLE: 0,
|
||||
PROFILE_RUNNING: 1,
|
||||
PROFILE_COMPLETED: 2
|
||||
};
|
@ -4,22 +4,36 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
var isJSM = typeof require !== "function";
|
||||
|
||||
// This code is needed because, for whatever reason, mochitest can't
|
||||
// find any requirejs module so we have to load it old school way. :(
|
||||
|
||||
if (isJSM) {
|
||||
var Cu = this["Components"].utils;
|
||||
let XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}).XPCOMUtils;
|
||||
this["loader"] = { lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils) };
|
||||
this["require"] = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
} else {
|
||||
var { Cu } = require("chrome");
|
||||
}
|
||||
|
||||
const { L10N_BUNDLE } = require("devtools/profiler/consts");
|
||||
|
||||
var EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
let EXPORTED_SYMBOLS = ["ProfilerController"];
|
||||
loader.lazyGetter(this, "L10N", () => new ViewHelpers.L10N(L10N_BUNDLE));
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
|
||||
"resource:///modules/devtools/gDevTools.jsm");
|
||||
loader.lazyGetter(this, "gDevTools",
|
||||
() => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
|
||||
"resource://gre/modules/devtools/dbg-server.jsm");
|
||||
loader.lazyGetter(this, "DebuggerServer",
|
||||
() => Cu.import("resource:///modules/devtools/dbg-server.jsm", {}).DebuggerServer);
|
||||
|
||||
/**
|
||||
* Data structure that contains information that has
|
||||
@ -44,7 +58,8 @@ function makeProfile(name, def={}) {
|
||||
return {
|
||||
name: name,
|
||||
timeStarted: def.timeStarted,
|
||||
timeEnded: def.timeEnded
|
||||
timeEnded: def.timeEnded,
|
||||
fromConsole: def.fromConsole || false
|
||||
};
|
||||
}
|
||||
|
||||
@ -75,6 +90,7 @@ function ProfilerController(target) {
|
||||
this.client = target.client;
|
||||
this.isConnected = false;
|
||||
this.consoleProfiles = [];
|
||||
this.reservedNames = {};
|
||||
|
||||
addTarget(target);
|
||||
|
||||
@ -86,9 +102,16 @@ function ProfilerController(target) {
|
||||
}
|
||||
|
||||
sharedData.controllers.set(target, this);
|
||||
EventEmitter.decorate(this);
|
||||
};
|
||||
|
||||
ProfilerController.prototype = {
|
||||
target: null,
|
||||
client: null,
|
||||
isConnected: null,
|
||||
consoleProfiles: null,
|
||||
reservedNames: null,
|
||||
|
||||
/**
|
||||
* Return a map of profile results for the current target.
|
||||
*
|
||||
@ -109,6 +132,19 @@ ProfilerController.prototype = {
|
||||
return profile.timeStarted !== null && profile.timeEnded === null;
|
||||
},
|
||||
|
||||
getProfileName: function PC_getProfileName() {
|
||||
let num = 1;
|
||||
let name = L10N.getFormatStr("profiler.profileName", [num]);
|
||||
|
||||
while (this.reservedNames[name]) {
|
||||
num += 1;
|
||||
name = L10N.getFormatStr("profiler.profileName", [num]);
|
||||
}
|
||||
|
||||
this.reservedNames[name] = true;
|
||||
return name;
|
||||
},
|
||||
|
||||
/**
|
||||
* A listener that fires whenever console.profile or console.profileEnd
|
||||
* is called.
|
||||
@ -117,26 +153,23 @@ ProfilerController.prototype = {
|
||||
* Type of a call. Either 'profile' or 'profileEnd'.
|
||||
* @param object data
|
||||
* Event data.
|
||||
* @param object panel
|
||||
* A reference to the ProfilerPanel in the current tab.
|
||||
*/
|
||||
onConsoleEvent: function (type, data, panel) {
|
||||
onConsoleEvent: function (type, data) {
|
||||
let name = data.extra.name;
|
||||
|
||||
let profileStart = () => {
|
||||
if (name && this.profiles.has(name))
|
||||
return;
|
||||
|
||||
// Add profile to the UI (createProfile will return
|
||||
// an automatically generated name if 'name' is falsey).
|
||||
let profile = panel.createProfile(name);
|
||||
profile.start((name, cb) => cb());
|
||||
|
||||
// Add profile structure to shared data.
|
||||
this.profiles.set(profile.name, makeProfile(profile.name, {
|
||||
timeStarted: data.extra.currentTime
|
||||
}));
|
||||
let profile = makeProfile(name || this.getProfileName(), {
|
||||
timeStarted: data.extra.currentTime,
|
||||
fromConsole: true
|
||||
});
|
||||
|
||||
this.profiles.set(profile.name, profile);
|
||||
this.consoleProfiles.push(profile.name);
|
||||
this.emit("profileStart", profile);
|
||||
};
|
||||
|
||||
let profileEnd = () => {
|
||||
@ -156,8 +189,6 @@ ProfilerController.prototype = {
|
||||
return;
|
||||
|
||||
let profileData = data.extra.profile;
|
||||
profile.timeEnded = data.extra.currentTime;
|
||||
|
||||
profileData.threads = profileData.threads.map((thread) => {
|
||||
let samples = thread.samples.filter((sample) => {
|
||||
return sample.time >= profile.timeStarted;
|
||||
@ -166,10 +197,10 @@ ProfilerController.prototype = {
|
||||
return { samples: samples };
|
||||
});
|
||||
|
||||
let ui = panel.getProfileByName(name);
|
||||
ui.data = profileData;
|
||||
ui.parse(profileData, () => panel.emit("parsed"));
|
||||
ui.stop((name, cb) => cb());
|
||||
profile.timeEnded = data.extra.currentTime;
|
||||
profile.data = profileData;
|
||||
|
||||
this.emit("profileEnd", profile);
|
||||
};
|
||||
|
||||
if (type === "profile")
|
||||
@ -217,27 +248,7 @@ ProfilerController.prototype = {
|
||||
if (toolbox == null)
|
||||
return;
|
||||
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
if (panel)
|
||||
return void this.onConsoleEvent(resp.subject.action, resp.data, panel);
|
||||
|
||||
// Can't use a promise here because of a race condition when the promise
|
||||
// is resolved only after -ready event is fired when creating a new panel
|
||||
// and during the -ready event when waiting for a panel to be created:
|
||||
//
|
||||
// console.profile(); // creates a new panel, waits for the promise
|
||||
// console.profileEnd(); // panel is not created yet but loading
|
||||
//
|
||||
// -> jsprofiler-ready event is fired which triggers a promise for profileEnd
|
||||
// -> a promise for profile is triggered.
|
||||
//
|
||||
// And it should be the other way around. Hence the event.
|
||||
|
||||
toolbox.once("jsprofiler-ready", (_, panel) => {
|
||||
this.onConsoleEvent(resp.subject.action, resp.data, panel);
|
||||
});
|
||||
|
||||
toolbox.loadTool("jsprofiler");
|
||||
this.onConsoleEvent(resp.subject.action, resp.data);
|
||||
});
|
||||
});
|
||||
|
||||
@ -392,3 +403,9 @@ ProfilerController.prototype = {
|
||||
this.actor = null;
|
||||
}
|
||||
};
|
||||
|
||||
if (isJSM) {
|
||||
var EXPORTED_SYMBOLS = ["ProfilerController"];
|
||||
} else {
|
||||
module.exports = ProfilerController;
|
||||
}
|
474
browser/devtools/profiler/panel.js
Normal file
474
browser/devtools/profiler/panel.js
Normal file
@ -0,0 +1,474 @@
|
||||
/* 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 { Cu } = require("chrome");
|
||||
const { PROFILE_IDLE, PROFILE_RUNNING, PROFILE_COMPLETED } = require("devtools/profiler/consts");
|
||||
|
||||
var EventEmitter = require("devtools/shared/event-emitter");
|
||||
var Promise = require("sdk/core/promise");
|
||||
var Cleopatra = require("devtools/profiler/cleopatra");
|
||||
var Sidebar = require("devtools/profiler/sidebar");
|
||||
var ProfilerController = require("devtools/profiler/controller");
|
||||
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm")
|
||||
|
||||
/**
|
||||
* Profiler panel. It is responsible for creating and managing
|
||||
* different profile instances (see cleopatra.js).
|
||||
*
|
||||
* ProfilerPanel is an event emitter. It can emit the following
|
||||
* events:
|
||||
*
|
||||
* - ready: after the panel is done loading everything,
|
||||
* including the default profile instance.
|
||||
* - started: after the panel successfuly starts our SPS
|
||||
* profiler.
|
||||
* - stopped: after the panel successfuly stops our SPS
|
||||
* profiler and is ready to hand over profiling
|
||||
* data
|
||||
* - parsed: after Cleopatra finishes parsing profiling
|
||||
* data.
|
||||
* - destroyed: after the panel cleans up after itself and
|
||||
* is ready to be destroyed.
|
||||
*
|
||||
* The following events are used mainly by tests to prevent
|
||||
* accidential oranges:
|
||||
*
|
||||
* - profileCreated: after a new profile is created.
|
||||
* - profileSwitched: after user switches to a different
|
||||
* profile.
|
||||
*/
|
||||
function ProfilerPanel(frame, toolbox) {
|
||||
this.isReady = false;
|
||||
this.window = frame.window;
|
||||
this.document = frame.document;
|
||||
this.target = toolbox.target;
|
||||
|
||||
this.profiles = new Map();
|
||||
this._uid = 0;
|
||||
this._msgQueue = {};
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
ProfilerPanel.prototype = {
|
||||
isReady: null,
|
||||
window: null,
|
||||
document: null,
|
||||
target: null,
|
||||
controller: null,
|
||||
profiles: null,
|
||||
sidebar: null,
|
||||
|
||||
_uid: null,
|
||||
_activeUid: null,
|
||||
_runningUid: null,
|
||||
_browserWin: null,
|
||||
_msgQueue: null,
|
||||
|
||||
get controls() {
|
||||
let doc = this.document;
|
||||
|
||||
return {
|
||||
get record() doc.querySelector("#profiler-start")
|
||||
};
|
||||
},
|
||||
|
||||
get activeProfile() {
|
||||
return this.profiles.get(this._activeUid);
|
||||
},
|
||||
|
||||
set activeProfile(profile) {
|
||||
if (this._activeUid === profile.uid)
|
||||
return;
|
||||
|
||||
if (this.activeProfile)
|
||||
this.activeProfile.hide();
|
||||
|
||||
this._activeUid = profile.uid;
|
||||
profile.show();
|
||||
},
|
||||
|
||||
set recordingProfile(profile) {
|
||||
let btn = this.controls.record;
|
||||
this._runningUid = profile ? profile.uid : null;
|
||||
|
||||
if (this._runningUid)
|
||||
btn.setAttribute("checked", true)
|
||||
else
|
||||
btn.removeAttribute("checked");
|
||||
},
|
||||
|
||||
get recordingProfile() {
|
||||
return this.profiles.get(this._runningUid);
|
||||
},
|
||||
|
||||
get browserWindow() {
|
||||
if (this._browserWin) {
|
||||
return this._browserWin;
|
||||
}
|
||||
|
||||
let win = this.window.top;
|
||||
let type = win.document.documentElement.getAttribute("windowtype");
|
||||
|
||||
if (type !== "navigator:browser") {
|
||||
win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
}
|
||||
|
||||
return this._browserWin = win;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open a debug connection and, on success, switch to the newly created
|
||||
* profile.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
open: function PP_open() {
|
||||
// Local profiling needs to make the target remote.
|
||||
let target = this.target;
|
||||
let promise = !target.isRemote ? target.makeRemote() : Promise.resolve(target);
|
||||
|
||||
return promise
|
||||
.then((target) => {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
this.controller = new ProfilerController(this.target);
|
||||
this.sidebar = new Sidebar(this.document.querySelector("#profiles-list"));
|
||||
|
||||
this.sidebar.widget.addEventListener("select", (ev) => {
|
||||
if (!ev.detail)
|
||||
return;
|
||||
|
||||
let profile = this.profiles.get(ev.detail.attachment.uid);
|
||||
this.activeProfile = profile;
|
||||
|
||||
if (profile.isReady) {
|
||||
return void this.emit("profileSwitched", profile.uid);
|
||||
}
|
||||
|
||||
profile.once("ready", () => {
|
||||
this.emit("profileSwitched", profile.uid);
|
||||
});
|
||||
});
|
||||
|
||||
this.controller.connect(() => {
|
||||
let btn = this.controls.record;
|
||||
btn.addEventListener("click", () => this.toggleRecording(), false);
|
||||
btn.removeAttribute("disabled");
|
||||
|
||||
// Import queued profiles.
|
||||
for (let [name, data] of this.controller.profiles) {
|
||||
let profile = this.createProfile(name);
|
||||
profile.isStarted = false;
|
||||
profile.isFinished = true;
|
||||
profile.data = data.data;
|
||||
profile.parse(data.data, () => this.emit("parsed"));
|
||||
|
||||
this.sidebar.setProfileState(profile, PROFILE_COMPLETED);
|
||||
if (!this.sidebar.selectedItem) {
|
||||
this.sidebar.selectedItem = this.sidebar.getItemByProfile(profile);
|
||||
}
|
||||
}
|
||||
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
deferred.resolve(this);
|
||||
});
|
||||
|
||||
this.controller.on("profileEnd", (_, data) => {
|
||||
let profile = this.createProfile(data.name);
|
||||
profile.isStarted = false;
|
||||
profile.isFinished = true;
|
||||
profile.data = data.data;
|
||||
profile.parse(data.data, () => this.emit("parsed"));
|
||||
|
||||
this.sidebar.setProfileState(profile, PROFILE_COMPLETED);
|
||||
if (!this.sidebar.selectedItem)
|
||||
this.sidebar.selectedItem = this.sidebar.getItemByProfile(profile);
|
||||
|
||||
if (this.recordingProfile && !data.fromConsole)
|
||||
this.recordingProfile = null;
|
||||
|
||||
this.emit("stopped");
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
})
|
||||
.then(null, (reason) =>
|
||||
Cu.reportError("ProfilePanel open failed: " + reason.message));
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new profile instance (see cleopatra.js) and
|
||||
* adds an appropriate item to the sidebar. Note that
|
||||
* this method doesn't automatically switch user to
|
||||
* the newly created profile, they have do to switch
|
||||
* explicitly.
|
||||
*
|
||||
* @param string name
|
||||
* (optional) name of the new profile
|
||||
*
|
||||
* @return ProfilerPanel
|
||||
*/
|
||||
createProfile: function (name) {
|
||||
if (name && this.getProfileByName(name)) {
|
||||
return this.getProfileByName(name);
|
||||
}
|
||||
|
||||
let uid = ++this._uid;
|
||||
let name = name || this.controller.getProfileName();
|
||||
let profile = new Cleopatra(uid, name, this);
|
||||
|
||||
this.profiles.set(uid, profile);
|
||||
this.sidebar.addProfile(profile);
|
||||
this.emit("profileCreated", uid);
|
||||
|
||||
return profile;
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts or stops profile recording.
|
||||
*/
|
||||
toggleRecording: function () {
|
||||
let profile = this.recordingProfile;
|
||||
|
||||
if (!profile) {
|
||||
profile = this.createProfile();
|
||||
|
||||
this.startProfiling(profile.name, () => {
|
||||
profile.isStarted = true;
|
||||
|
||||
this.sidebar.setProfileState(profile, PROFILE_RUNNING);
|
||||
this.recordingProfile = profile;
|
||||
this.emit("started");
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.stopProfiling(profile.name, (data) => {
|
||||
profile.isStarted = false;
|
||||
profile.isFinished = true;
|
||||
profile.data = data;
|
||||
profile.parse(data, () => this.emit("parsed"));
|
||||
|
||||
this.sidebar.setProfileState(profile, PROFILE_COMPLETED);
|
||||
this.activeProfile = profile;
|
||||
this.sidebar.selectedItem = this.sidebar.getItemByProfile(profile);
|
||||
this.recordingProfile = null;
|
||||
this.emit("stopped");
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Start collecting profile data.
|
||||
*
|
||||
* @param function onStart
|
||||
* A function to call once we get the message
|
||||
* that profiling had been successfuly started.
|
||||
*/
|
||||
startProfiling: function (name, onStart) {
|
||||
this.controller.start(name, (err) => {
|
||||
if (err) {
|
||||
return void Cu.reportError("ProfilerController.start: " + err.message);
|
||||
}
|
||||
|
||||
onStart();
|
||||
this.emit("started");
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop collecting profile data.
|
||||
*
|
||||
* @param function onStop
|
||||
* A function to call once we get the message
|
||||
* that profiling had been successfuly stopped.
|
||||
*/
|
||||
stopProfiling: function (name, onStop) {
|
||||
this.controller.isActive((err, isActive) => {
|
||||
if (err) {
|
||||
Cu.reportError("ProfilerController.isActive: " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.controller.stop(name, (err, data) => {
|
||||
if (err) {
|
||||
Cu.reportError("ProfilerController.stop: " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
onStop(data);
|
||||
this.emit("stopped", data);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Lookup an individual profile by its name.
|
||||
*
|
||||
* @param string name name of the profile
|
||||
* @return profile object or null
|
||||
*/
|
||||
getProfileByName: function PP_getProfileByName(name) {
|
||||
if (!this.profiles) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let [ uid, profile ] of this.profiles) {
|
||||
if (profile.name === name) {
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Lookup an individual profile by its UID.
|
||||
*
|
||||
* @param number uid UID of the profile
|
||||
* @return profile object or null
|
||||
*/
|
||||
getProfileByUID: function PP_getProfileByUID(uid) {
|
||||
if (!this.profiles) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.profiles.get(uid) || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterates over each available profile and calls
|
||||
* a callback with it as a parameter.
|
||||
*
|
||||
* @param function cb a callback to call
|
||||
*/
|
||||
eachProfile: function PP_eachProfile(cb) {
|
||||
let uid = this._uid;
|
||||
|
||||
if (!this.profiles) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (uid >= 0) {
|
||||
if (this.profiles.has(uid)) {
|
||||
cb(this.profiles.get(uid));
|
||||
}
|
||||
|
||||
uid -= 1;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Broadcast messages to all Cleopatra instances.
|
||||
*
|
||||
* @param number target
|
||||
* UID of the recepient profile. All profiles will receive the message
|
||||
* but the profile specified by 'target' will have a special property,
|
||||
* isCurrent, set to true.
|
||||
* @param object data
|
||||
* An object with a property 'task' that will be sent over to Cleopatra.
|
||||
*/
|
||||
broadcast: function PP_broadcast(target, data) {
|
||||
if (!this.profiles) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.eachProfile((profile) => {
|
||||
profile.message({
|
||||
uid: target,
|
||||
isCurrent: target === profile.uid,
|
||||
task: data.task
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Open file specified in data in either a debugger or view-source.
|
||||
*
|
||||
* @param object data
|
||||
* An object describing the file. It must have three properties:
|
||||
* - uri
|
||||
* - line
|
||||
* - isChrome (chrome files are opened via view-source)
|
||||
*/
|
||||
displaySource: function PP_displaySource(data, onOpen=function() {}) {
|
||||
let win = this.window;
|
||||
let panelWin, timeout;
|
||||
|
||||
function onSourceShown(event) {
|
||||
if (event.detail.url !== data.uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
panelWin.removeEventListener("Debugger:SourceShown", onSourceShown, false);
|
||||
panelWin.editor.setCaretPosition(data.line - 1);
|
||||
onOpen();
|
||||
}
|
||||
|
||||
if (data.isChrome) {
|
||||
return void this.browserWindow.gViewSourceUtils.
|
||||
viewSource(data.uri, null, this.document, data.line);
|
||||
}
|
||||
|
||||
gDevTools.showToolbox(this.target, "jsdebugger").then(function (toolbox) {
|
||||
let dbg = toolbox.getCurrentPanel();
|
||||
panelWin = dbg.panelWin;
|
||||
|
||||
let view = dbg.panelWin.DebuggerView;
|
||||
if (view.Sources.selectedValue === data.uri) {
|
||||
view.editor.setCaretPosition(data.line - 1);
|
||||
onOpen();
|
||||
return;
|
||||
}
|
||||
|
||||
panelWin.addEventListener("Debugger:SourceShown", onSourceShown, false);
|
||||
panelWin.DebuggerView.Sources.preferredSource = data.uri;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup.
|
||||
*/
|
||||
destroy: function PP_destroy() {
|
||||
if (this.profiles) {
|
||||
let uid = this._uid;
|
||||
|
||||
while (uid >= 0) {
|
||||
if (this.profiles.has(uid)) {
|
||||
this.profiles.get(uid).destroy();
|
||||
this.profiles.delete(uid);
|
||||
}
|
||||
uid -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.controller) {
|
||||
this.controller.destroy();
|
||||
}
|
||||
|
||||
this.isReady = null;
|
||||
this.window = null;
|
||||
this.document = null;
|
||||
this.target = null;
|
||||
this.controller = null;
|
||||
this.profiles = null;
|
||||
this._uid = null;
|
||||
this._activeUid = null;
|
||||
|
||||
this.emit("destroyed");
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ProfilerPanel;
|
@ -19,10 +19,9 @@
|
||||
<box flex="1" id="profiler-chrome" class="devtools-responsive-container">
|
||||
<vbox class="profiler-sidebar">
|
||||
<toolbar class="devtools-toolbar">
|
||||
<toolbarbutton id="profiler-create"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&profilerNew.label;"
|
||||
disabled="true"/>
|
||||
<hbox id="profiler-controls">
|
||||
<toolbarbutton id="profiler-start" class="devtools-toolbarbutton"/>
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
<vbox id="profiles-list" flex="1">
|
||||
|
86
browser/devtools/profiler/sidebar.js
Normal file
86
browser/devtools/profiler/sidebar.js
Normal file
@ -0,0 +1,86 @@
|
||||
/* 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";
|
||||
|
||||
let { Cu } = require("chrome");
|
||||
let EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
const {
|
||||
PROFILE_IDLE,
|
||||
PROFILE_COMPLETED,
|
||||
PROFILE_RUNNING,
|
||||
L10N_BUNDLE
|
||||
} = require("devtools/profiler/consts");
|
||||
|
||||
loader.lazyGetter(this, "L10N", () => new ViewHelpers.L10N(L10N_BUNDLE));
|
||||
|
||||
function Sidebar(el) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.document = el.ownerDocument;
|
||||
this.widget = new SideMenuWidget(el);
|
||||
this.widget.notice = L10N.getStr("profiler.sidebarNotice");
|
||||
}
|
||||
|
||||
Sidebar.prototype = Heritage.extend(WidgetMethods, {
|
||||
addProfile: function (profile) {
|
||||
let doc = this.document;
|
||||
let box = doc.createElement("vbox");
|
||||
let h3 = doc.createElement("h3");
|
||||
let span = doc.createElement("span");
|
||||
|
||||
box.id = "profile-" + profile.uid;
|
||||
box.className = "profiler-sidebar-item";
|
||||
|
||||
h3.textContent = profile.name;
|
||||
span.textContent = L10N.getStr("profiler.stateIdle");
|
||||
|
||||
box.appendChild(h3);
|
||||
box.appendChild(span);
|
||||
|
||||
this.push([box], {
|
||||
attachment: {
|
||||
uid: profile.uid,
|
||||
name: profile.name,
|
||||
state: PROFILE_IDLE
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getElementByProfile: function (profile) {
|
||||
return this.document.querySelector("#profile-" + profile.uid);
|
||||
},
|
||||
|
||||
getItemByProfile: function (profile) {
|
||||
return this.getItemForPredicate(item => item.attachment.uid === profile.uid);
|
||||
},
|
||||
|
||||
setProfileState: function (profile, state) {
|
||||
let item = this.getItemByProfile(profile);
|
||||
let label = item.target.querySelector(".profiler-sidebar-item > span");
|
||||
|
||||
switch (state) {
|
||||
case PROFILE_IDLE:
|
||||
label.textContent = L10N.getStr("profiler.stateIdle");
|
||||
break;
|
||||
case PROFILE_RUNNING:
|
||||
label.textContent = L10N.getStr("profiler.stateRunning");
|
||||
break;
|
||||
case PROFILE_COMPLETED:
|
||||
label.textContent = L10N.getStr("profiler.stateCompleted");
|
||||
break;
|
||||
default: // Wrong state, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
item.attachment.state = state;
|
||||
this.emit("stateChanged", item);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Sidebar;
|
@ -11,18 +11,17 @@ relativesrcdir = @relativesrcdir@
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
MOCHITEST_BROWSER_TESTS = \
|
||||
browser_profiler_profiles.js \
|
||||
browser_profiler_remote.js \
|
||||
browser_profiler_bug_834878_source_buttons.js \
|
||||
browser_profiler_cmd.js \
|
||||
browser_profiler_run.js \
|
||||
browser_profiler_controller.js \
|
||||
browser_profiler_bug_830664_multiple_profiles.js \
|
||||
browser_profiler_bug_855244_multiple_tabs.js \
|
||||
browser_profiler_console_api.js \
|
||||
browser_profiler_console_api_named.js \
|
||||
browser_profiler_console_api_mixed.js \
|
||||
browser_profiler_console_api_content.js \
|
||||
browser_profiler_escape.js \
|
||||
head.js \
|
||||
$(NULL)
|
||||
|
||||
|
@ -1,63 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel, gUid;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function onSetUp(tab, browser, panel) {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
gPanel.once("profileCreated", function (_, uid) {
|
||||
gUid = uid;
|
||||
let profile = gPanel.profiles.get(uid);
|
||||
|
||||
if (profile.isReady) {
|
||||
startProfiling();
|
||||
} else {
|
||||
profile.once("ready", startProfiling);
|
||||
}
|
||||
});
|
||||
gPanel.createProfile();
|
||||
});
|
||||
}
|
||||
|
||||
function startProfiling() {
|
||||
gPanel.profiles.get(gPanel.activeProfile.uid).once("started", function () {
|
||||
setTimeout(function () {
|
||||
sendFromProfile(2, "start");
|
||||
gPanel.profiles.get(2).once("started", function () setTimeout(stopProfiling, 50));
|
||||
}, 50);
|
||||
});
|
||||
|
||||
sendFromProfile(gPanel.activeProfile.uid, "start");
|
||||
}
|
||||
|
||||
function stopProfiling() {
|
||||
is(getSidebarItem(1).attachment.state, PROFILE_RUNNING);
|
||||
is(getSidebarItem(2).attachment.state, PROFILE_RUNNING);
|
||||
|
||||
gPanel.profiles.get(gPanel.activeProfile.uid).once("stopped", function () {
|
||||
is(getSidebarItem(1).attachment.state, PROFILE_COMPLETED);
|
||||
|
||||
sendFromProfile(2, "stop");
|
||||
gPanel.profiles.get(2).once("stopped", confirmAndFinish);
|
||||
});
|
||||
|
||||
sendFromProfile(gPanel.activeProfile.uid, "stop");
|
||||
}
|
||||
|
||||
function confirmAndFinish(ev, data) {
|
||||
is(getSidebarItem(1).attachment.state, PROFILE_COMPLETED);
|
||||
is(getSidebarItem(2).attachment.state, PROFILE_COMPLETED);
|
||||
|
||||
tearDown(gTab, function onTearDown() {
|
||||
gPanel = null;
|
||||
gTab = null;
|
||||
gUid = null;
|
||||
});
|
||||
}
|
@ -9,30 +9,26 @@ function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function onSetUp(tab, browser, panel) {
|
||||
panel.once("profileCreated", function () {
|
||||
let data = { uri: SCRIPT, line: 5, isChrome: false };
|
||||
let data = { uri: SCRIPT, line: 5, isChrome: false };
|
||||
|
||||
panel.displaySource(data, function onOpen() {
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let dbg = gDevTools.getToolbox(target).getPanel("jsdebugger");
|
||||
let view = dbg.panelWin.DebuggerView;
|
||||
panel.displaySource(data, function onOpen() {
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let dbg = gDevTools.getToolbox(target).getPanel("jsdebugger");
|
||||
let view = dbg.panelWin.DebuggerView;
|
||||
|
||||
is(view.Sources.selectedValue, data.uri, "URI is different");
|
||||
is(view.editor.getCaretPosition().line, data.line - 1,
|
||||
"Line is different");
|
||||
is(view.Sources.selectedValue, data.uri, "URI is different");
|
||||
is(view.editor.getCaretPosition().line, data.line - 1,
|
||||
"Line is different");
|
||||
|
||||
// Test the case where script is already loaded.
|
||||
view.editor.setCaretPosition(1);
|
||||
gDevTools.showToolbox(target, "jsprofiler").then(function () {
|
||||
panel.displaySource(data, function onOpenAgain() {
|
||||
is(view.editor.getCaretPosition().line, data.line - 1,
|
||||
"Line is different");
|
||||
tearDown(tab);
|
||||
});
|
||||
// Test the case where script is already loaded.
|
||||
view.editor.setCaretPosition(1);
|
||||
gDevTools.showToolbox(target, "jsprofiler").then(function () {
|
||||
panel.displaySource(data, function onOpenAgain() {
|
||||
is(view.editor.getCaretPosition().line, data.line - 1,
|
||||
"Line is different");
|
||||
tearDown(tab);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
panel.createProfile();
|
||||
});
|
||||
}
|
||||
|
@ -41,54 +41,41 @@ function testProfilerStart() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
gPanel.once("started", function () {
|
||||
is(gPanel.profiles.size, 2, "There are two profiles");
|
||||
ok(!gPanel.getProfileByName("Profile 1").isStarted, "Profile 1 wasn't started");
|
||||
ok(gPanel.getProfileByName("Profile 2").isStarted, "Profile 2 was started");
|
||||
cmd('profiler start "Profile 2"', "This profile has already been started");
|
||||
is(gPanel.profiles.size, 1, "There is a new profile");
|
||||
is(gPanel.getProfileByName("Profile 1"), gPanel.recordingProfile, "Recording profile is OK");
|
||||
ok(!gPanel.activeProfile, "There's no active profile yet");
|
||||
cmd("profiler start", gcli.lookup("profilerAlreadyStarted2"));
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
cmd("profiler start", gcli.lookup("profilerStarting2"));
|
||||
cmd("profiler start", gcli.lookup("profilerStarted"));
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testProfilerList() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
cmd("profiler list", /^.*Profile\s1.*Profile\s2\s\*.*$/);
|
||||
deferred.resolve();
|
||||
|
||||
return deferred.promise;
|
||||
cmd("profiler list", /^.*Profile\s1\s\*.*$/);
|
||||
}
|
||||
|
||||
function testProfilerStop() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
gPanel.once("stopped", function () {
|
||||
ok(!gPanel.getProfileByName("Profile 2").isStarted, "Profile 2 was stopped");
|
||||
ok(gPanel.getProfileByName("Profile 2").isFinished, "Profile 2 was stopped");
|
||||
cmd('profiler stop "Profile 2"', "This profile has already been completed. " +
|
||||
"Use 'profile show' command to see its results");
|
||||
cmd('profiler stop "Profile 1"', "This profile has not been started yet. " +
|
||||
"Use 'profile start' to start profiling");
|
||||
cmd('profiler stop "invalid"', "Profile not found")
|
||||
is(gPanel.activeProfile, gPanel.getProfileByName("Profile 1"), "Active profile is OK");
|
||||
ok(!gPanel.recordingProfile, "There's no recording profile");
|
||||
cmd("profiler stop", gcli.lookup("profilerNotStarted3"));
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
cmd('profiler stop "Profile 2"', gcli.lookup("profilerStopping2"));
|
||||
cmd("profiler stop");
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testProfilerShow() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
is(gPanel.getProfileByName("Profile 2").uid, gPanel.activeProfile.uid,
|
||||
"Profile 2 is active");
|
||||
|
||||
gPanel.once("profileSwitched", function () {
|
||||
is(gPanel.getProfileByName("Profile 1").uid, gPanel.activeProfile.uid,
|
||||
"Profile 1 is active");
|
||||
cmd('profile show "invalid"', "Profile not found");
|
||||
is(gPanel.getProfileByName("Profile 1"), gPanel.activeProfile, "Profile 1 is active");
|
||||
cmd('profile show "invalid"', gcli.lookup("profilerNotFound"));
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
|
@ -19,46 +19,17 @@ function test() {
|
||||
function testConsoleProfile(hud) {
|
||||
hud.jsterm.clearOutput(true);
|
||||
|
||||
// Here we start two named profiles and then end one of them.
|
||||
// profileEnd, when name is not provided, simply pops the latest
|
||||
// profile.
|
||||
|
||||
let profilesStarted = 0;
|
||||
|
||||
function profileEnd(_, uid) {
|
||||
let profile = gPanel.profiles.get(uid);
|
||||
gPanel.once("parsed", () => {
|
||||
let profile = gPanel.activeProfile;
|
||||
|
||||
profile.once("started", () => {
|
||||
if (++profilesStarted < 2)
|
||||
return;
|
||||
|
||||
gPanel.off("profileCreated", profileEnd);
|
||||
gPanel.profiles.get(3).once("stopped", () => {
|
||||
openProfiler(gTab, checkProfiles);
|
||||
});
|
||||
|
||||
hud.jsterm.execute("console.profileEnd()");
|
||||
});
|
||||
}
|
||||
|
||||
gPanel.on("profileCreated", profileEnd);
|
||||
hud.jsterm.execute("console.profile()");
|
||||
hud.jsterm.execute("console.profile()");
|
||||
}
|
||||
|
||||
function checkProfiles(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_IDLE);
|
||||
is(getSidebarItem(2, panel).attachment.state, PROFILE_RUNNING);
|
||||
is(getSidebarItem(3, panel).attachment.state, PROFILE_COMPLETED);
|
||||
|
||||
// Make sure we can still stop profiles via the UI.
|
||||
|
||||
gPanel.profiles.get(2).once("stopped", () => {
|
||||
is(getSidebarItem(2, panel).attachment.state, PROFILE_COMPLETED);
|
||||
is(profile.name, "Profile 1", "Profile name is OK");
|
||||
is(gPanel.sidebar.selectedItem, gPanel.sidebar.getItemByProfile(profile), "Sidebar is OK");
|
||||
is(gPanel.sidebar.selectedItem.attachment.state, PROFILE_COMPLETED);
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
|
||||
sendFromProfile(2, "stop");
|
||||
hud.jsterm.execute("console.profile()");
|
||||
hud.jsterm.execute("console.profileEnd()");
|
||||
}
|
@ -29,8 +29,7 @@ function test() {
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
is(getSidebarItem(1).attachment.state, PROFILE_IDLE);
|
||||
is(getSidebarItem(2).attachment.state, PROFILE_COMPLETED);
|
||||
is(getSidebarItem(1).attachment.state, PROFILE_COMPLETED);
|
||||
|
||||
gPanel.once("parsed", () => {
|
||||
function assertSampleAndFinish() {
|
||||
|
@ -18,12 +18,13 @@ function test() {
|
||||
|
||||
function runTests(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
let record = gPanel.controls.record;
|
||||
|
||||
panel.profiles.get(1).once("started", () => {
|
||||
panel.once("started", () => {
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_RUNNING);
|
||||
|
||||
openConsole(gTab, (hud) => {
|
||||
panel.profiles.get(1).once("stopped", () => {
|
||||
panel.once("stopped", () => {
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_COMPLETED);
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
@ -32,5 +33,5 @@ function runTests(toolbox) {
|
||||
});
|
||||
});
|
||||
|
||||
sendFromProfile(1, "start");
|
||||
record.click();
|
||||
}
|
@ -23,23 +23,16 @@ function testConsoleProfile(hud) {
|
||||
|
||||
let profilesStarted = 0;
|
||||
|
||||
function profileEnd(_, uid) {
|
||||
let profile = gPanel.profiles.get(uid);
|
||||
function endProfile() {
|
||||
if (++profilesStarted < 2)
|
||||
return;
|
||||
|
||||
profile.once("started", () => {
|
||||
if (++profilesStarted < 2)
|
||||
return;
|
||||
|
||||
gPanel.off("profileCreated", profileEnd);
|
||||
gPanel.profiles.get(2).once("stopped", () => {
|
||||
openProfiler(gTab, checkProfiles);
|
||||
});
|
||||
|
||||
hud.jsterm.execute("console.profileEnd('Second')");
|
||||
});
|
||||
gPanel.controller.off("profileStart", endProfile);
|
||||
gPanel.controller.once("profileEnd", () => openProfiler(gTab, checkProfiles));
|
||||
hud.jsterm.execute("console.profileEnd('Second')");
|
||||
}
|
||||
|
||||
gPanel.on("profileCreated", profileEnd);
|
||||
gPanel.controller.on("profileStart", endProfile);
|
||||
hud.jsterm.execute("console.profile('Second')");
|
||||
hud.jsterm.execute("console.profile('Third')");
|
||||
}
|
||||
@ -47,17 +40,14 @@ function testConsoleProfile(hud) {
|
||||
function checkProfiles(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_IDLE);
|
||||
is(getSidebarItem(2, panel).attachment.name, "Second");
|
||||
is(getSidebarItem(2, panel).attachment.state, PROFILE_COMPLETED);
|
||||
is(getSidebarItem(3, panel).attachment.name, "Third");
|
||||
is(getSidebarItem(3, panel).attachment.state, PROFILE_RUNNING);
|
||||
is(getSidebarItem(1, panel).attachment.name, "Second", "Name in sidebar is OK");
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_COMPLETED, "State in sidebar is OK");
|
||||
|
||||
// Make sure we can still stop profiles via the queue pop.
|
||||
|
||||
gPanel.profiles.get(3).once("stopped", () => {
|
||||
gPanel.controller.once("profileEnd", () => {
|
||||
openProfiler(gTab, () => {
|
||||
is(getSidebarItem(3, panel).attachment.state, PROFILE_COMPLETED);
|
||||
is(getSidebarItem(2, panel).attachment.state, PROFILE_COMPLETED, "State in sidebar is OK");
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
});
|
||||
|
43
browser/devtools/profiler/test/browser_profiler_escape.js
Normal file
43
browser/devtools/profiler/test/browser_profiler_escape.js
Normal file
@ -0,0 +1,43 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function (tab, browser, panel) {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
let record = gPanel.controls.record;
|
||||
|
||||
gPanel.once("started", () => {
|
||||
gPanel.once("stopped", () => {
|
||||
let [ win, doc ] = getProfileInternals(gPanel.activeProfile.uid);
|
||||
|
||||
let expl = "<script>function f() {}</script></textarea><img/src='about:logo'>";
|
||||
let expl2 = "<script>function f() {}</script></pre><img/src='about:logo'>";
|
||||
|
||||
is(win.escapeHTML(expl),
|
||||
"<script>function f() {}</script></textarea><img/src='about:logo'>");
|
||||
|
||||
is(win.escapeHTML(expl2),
|
||||
"<script>function f() {}</script></pre><img/src='about:logo'>");
|
||||
|
||||
tearDown(gTab, () => {
|
||||
gTab = null;
|
||||
gPanel = null;
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
record.click();
|
||||
}, 50);
|
||||
});
|
||||
|
||||
record.click();
|
||||
});
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function onSetUp(tab, browser, panel) {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
panel.once("profileCreated", onProfileCreated);
|
||||
panel.once("profileSwitched", onProfileSwitched);
|
||||
|
||||
testNewProfile();
|
||||
});
|
||||
}
|
||||
|
||||
function testNewProfile() {
|
||||
is(gPanel.profiles.size, 1, "There is only one profile");
|
||||
|
||||
let btn = gPanel.document.getElementById("profiler-create");
|
||||
ok(!btn.getAttribute("disabled"), "Create Profile button is not disabled");
|
||||
btn.click();
|
||||
}
|
||||
|
||||
function onProfileCreated(name, uid) {
|
||||
is(gPanel.profiles.size, 2, "There are two profiles now");
|
||||
ok(gPanel.activeProfile.uid !== uid, "New profile is not yet active");
|
||||
|
||||
let btn = gPanel.document.getElementById("profile-" + uid);
|
||||
ok(btn, "Profile item has been added to the sidebar");
|
||||
btn.click();
|
||||
}
|
||||
|
||||
function onProfileSwitched(name, uid) {
|
||||
gPanel.once("profileCreated", onNamedProfileCreated);
|
||||
gPanel.once("profileSwitched", onNamedProfileSwitched);
|
||||
|
||||
ok(gPanel.activeProfile.uid === uid, "Switched to a new profile");
|
||||
gPanel.createProfile("Custom Profile");
|
||||
}
|
||||
|
||||
function onNamedProfileCreated(name, uid) {
|
||||
is(gPanel.profiles.size, 3, "There are three profiles now");
|
||||
is(gPanel.getProfileByUID(uid).name, "Custom Profile", "Name is correct");
|
||||
|
||||
let profile = gPanel.profiles.get(uid);
|
||||
let data = gPanel.sidebar.getItemByProfile(profile).attachment;
|
||||
|
||||
is(data.uid, uid, "UID is correct");
|
||||
is(data.name, "Custom Profile", "Name is correct on the label");
|
||||
|
||||
let btn = gPanel.document.getElementById("profile-" + uid);
|
||||
ok(btn, "Profile item has been added to the sidebar");
|
||||
btn.click();
|
||||
}
|
||||
|
||||
function onNamedProfileSwitched(name, uid) {
|
||||
ok(gPanel.activeProfile.uid === uid, "Switched to a new profile");
|
||||
|
||||
tearDown(gTab, function onTearDown() {
|
||||
gPanel = null;
|
||||
gTab = null;
|
||||
});
|
||||
}
|
@ -12,7 +12,7 @@ Cu.import("resource://gre/modules/devtools/dbg-client.jsm", temp);
|
||||
let DebuggerClient = temp.DebuggerClient;
|
||||
let debuggerSocketConnect = temp.debuggerSocketConnect;
|
||||
|
||||
Cu.import("resource:///modules/devtools/ProfilerController.jsm", temp);
|
||||
Cu.import("resource:///modules/devtools/profiler/controller.js", temp);
|
||||
let ProfilerController = temp.ProfilerController;
|
||||
|
||||
function test() {
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel, gAttempts = 0;
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
@ -12,55 +12,104 @@ function test() {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
panel.once("started", onStart);
|
||||
panel.once("parsed", onParsed);
|
||||
|
||||
testUI();
|
||||
});
|
||||
}
|
||||
|
||||
function testUI() {
|
||||
ok(gPanel, "Profiler panel exists");
|
||||
ok(gPanel.activeProfile, "Active profile exists");
|
||||
|
||||
let [win, doc] = getProfileInternals();
|
||||
let startButton = doc.querySelector(".controlPane #startWrapper button");
|
||||
let stopButton = doc.querySelector(".controlPane #stopWrapper button");
|
||||
|
||||
ok(startButton, "Start button exists");
|
||||
ok(stopButton, "Stop button exists");
|
||||
|
||||
startButton.click();
|
||||
}
|
||||
|
||||
function onStart() {
|
||||
gPanel.controller.isActive(function (err, isActive) {
|
||||
ok(isActive, "Profiler is running");
|
||||
|
||||
let [win, doc] = getProfileInternals();
|
||||
let stopButton = doc.querySelector(".controlPane #stopWrapper button");
|
||||
|
||||
setTimeout(function () stopButton.click(), 100);
|
||||
});
|
||||
}
|
||||
|
||||
function onParsed() {
|
||||
function assertSample() {
|
||||
let [win,doc] = getProfileInternals();
|
||||
let sample = doc.getElementsByClassName("samplePercentage");
|
||||
|
||||
if (sample.length <= 0) {
|
||||
return void setTimeout(assertSample, 100);
|
||||
function done() {
|
||||
tearDown(gTab, () => { gPanel = null; gTab = null; });
|
||||
}
|
||||
|
||||
ok(sample.length > 0, "We have some items displayed");
|
||||
is(sample[0].innerHTML, "100.0%", "First percentage is 100%");
|
||||
|
||||
tearDown(gTab, function onTearDown() {
|
||||
gPanel = null;
|
||||
gTab = null;
|
||||
});
|
||||
}
|
||||
|
||||
assertSample();
|
||||
startRecording()
|
||||
.then(stopRecording)
|
||||
.then(startRecordingAgain)
|
||||
.then(stopRecording)
|
||||
.then(switchBackToTheFirstOne)
|
||||
.then(done);
|
||||
});
|
||||
}
|
||||
|
||||
function startRecording() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
ok(gPanel, "Profiler panel exists");
|
||||
ok(!gPanel.activeProfile, "Active profile doesn't exist");
|
||||
ok(!gPanel.recordingProfile, "Recording profile doesn't exist");
|
||||
|
||||
let record = gPanel.controls.record;
|
||||
ok(record, "Record button exists.");
|
||||
ok(!record.getAttribute("checked"), "Record button is unchecked");
|
||||
|
||||
gPanel.once("started", () => {
|
||||
let item = gPanel.sidebar.getItemByProfile(gPanel.recordingProfile);
|
||||
is(item.attachment.name, "Profile 1");
|
||||
is(item.attachment.state, PROFILE_RUNNING);
|
||||
|
||||
gPanel.controller.isActive(function (err, isActive) {
|
||||
ok(isActive, "Profiler is running");
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
record.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function stopRecording() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
gPanel.once("parsed", () => {
|
||||
let item = gPanel.sidebar.getItemByProfile(gPanel.activeProfile);
|
||||
is(item.attachment.state, PROFILE_COMPLETED);
|
||||
|
||||
function assertSample() {
|
||||
let [ win, doc ] = getProfileInternals();
|
||||
let sample = doc.getElementsByClassName("samplePercentage");
|
||||
|
||||
if (sample.length <= 0) {
|
||||
return void setTimeout(assertSample, 100);
|
||||
}
|
||||
|
||||
ok(sample.length > 0, "We have some items displayed");
|
||||
is(sample[0].innerHTML, "100.0%", "First percentage is 100%");
|
||||
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
assertSample();
|
||||
});
|
||||
|
||||
setTimeout(function () gPanel.controls.record.click(), 100);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function startRecordingAgain() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let record = gPanel.controls.record;
|
||||
ok(!record.getAttribute("checked"), "Record button is unchecked");
|
||||
|
||||
gPanel.once("started", () => {
|
||||
ok(gPanel.activeProfile !== gPanel.recordingProfile);
|
||||
|
||||
let item = gPanel.sidebar.getItemByProfile(gPanel.recordingProfile);
|
||||
is(item.attachment.name, "Profile 2");
|
||||
is(item.attachment.state, PROFILE_RUNNING);
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
record.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function switchBackToTheFirstOne() {
|
||||
let deferred = Promise.defer();
|
||||
let button = gPanel.sidebar.getElementByProfile({ uid: 1 });
|
||||
let item = gPanel.sidebar.getItemByProfile({ uid: 1 });
|
||||
|
||||
gPanel.once("profileSwitched", () => {
|
||||
is(gPanel.activeProfile.uid, 1, "activeProfile is correct");
|
||||
is(gPanel.sidebar.selectedItem, item, "selectedItem is correct");
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
button.click();
|
||||
return deferred.promise;
|
||||
}
|
@ -1238,13 +1238,7 @@ profilerShowManual=Name of a profile.
|
||||
# LOCALIZATION NOTE (profilerAlreadyStarted) A message that is displayed whenever
|
||||
# an operation cannot be completed because the profile in question has already
|
||||
# been started.
|
||||
profilerAlreadyStarted=This profile has already been started
|
||||
|
||||
# LOCALIZATION NOTE (profilerAlreadyFinished) A message that is displayed whenever
|
||||
# an operation cannot be completed because the profile in question has already
|
||||
# been finished. It also contains a hint to use the 'profile show' command to see
|
||||
# the profiling results.
|
||||
profilerAlreadyFinished=This profile has already been completed. Use 'profile show' command to see its results
|
||||
profilerAlreadyStarted2=Profile has already been started
|
||||
|
||||
# LOCALIZATION NOTE (profilerNotFound) A message that is displayed whenever
|
||||
# an operation cannot be completed because the profile in question could not be
|
||||
@ -1255,15 +1249,11 @@ profilerNotFound=Profile not found
|
||||
# an operation cannot be completed because the profile in question has not been
|
||||
# started yet. It also contains a hint to use the 'profile start' command to
|
||||
# start the profiler.
|
||||
profilerNotStarted2=This profile has not been started yet. Use 'profile start' to start profiling
|
||||
profilerNotStarted3=Profiler has not been started yet. Use 'profile start' to start profiling
|
||||
|
||||
# LOCALIZATION NOTE (profilerStarting) A very short string that indicates that
|
||||
# we're starting the profiler.
|
||||
profilerStarting2=Starting…
|
||||
|
||||
# LOCALIZATION NOTE (profilerStopping) A very short string that indicates that
|
||||
# we're stopping the profiler.
|
||||
profilerStopping2=Stopping…
|
||||
# LOCALIZATION NOTE (profilerStarted) A very short string that indicates that
|
||||
# we have started recording.
|
||||
profilerStarted=Recording...
|
||||
|
||||
# LOCALIZATION NOTE (profilerNotReady) A message that is displayed whenever
|
||||
# an operation cannot be completed because the profiler has not been opened yet.
|
||||
|
@ -13,3 +13,11 @@
|
||||
<!-- LOCALIZATION NOTE (profilerNew.label): This is the label for the
|
||||
- button that creates a new profile. -->
|
||||
<!ENTITY profilerNew.label "New">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerStart.label): This is the label for the
|
||||
- button that starts the profiler. -->
|
||||
<!ENTITY profilerStart.label "Start">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerStop.label): This is the label for the
|
||||
- button that stops the profiler. -->
|
||||
<!ENTITY profilerStop.label "Stop">
|
@ -97,3 +97,9 @@ profiler.stateRunning=Running
|
||||
# This string is used to show that the profile in question is in COMPLETED
|
||||
# state meaning that it has been started and stopped already.
|
||||
profiler.stateCompleted=Completed
|
||||
|
||||
# LOCALIZATION NOTE (profiler.sidebarNotice)
|
||||
# This string is displayed in the profiler sidebar when there are no
|
||||
# existing profiles to show (usually happens when the user opens the
|
||||
# profiler for the first time).
|
||||
profiler.sidebarNotice=There are no profiles yet.
|
@ -200,6 +200,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
|
||||
skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
|
||||
skin/classic/browser/devtools/inspector.css (devtools/inspector.css)
|
||||
skin/classic/browser/devtools/profiler-stopwatch.png (devtools/profiler-stopwatch.png)
|
||||
skin/classic/browser/devtools/toolbox.css (devtools/toolbox.css)
|
||||
skin/classic/browser/devtools/tool-options.png (devtools/tool-options.png)
|
||||
skin/classic/browser/devtools/tool-webconsole.png (devtools/tool-webconsole.png)
|
||||
|
0
browser/themes/osx/devtools/profiler-stopwatch.png
Normal file
0
browser/themes/osx/devtools/profiler-stopwatch.png
Normal file
@ -286,6 +286,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/dock-bottom.png (devtools/dock-bottom.png)
|
||||
skin/classic/browser/devtools/dock-side.png (devtools/dock-side.png)
|
||||
* skin/classic/browser/devtools/inspector.css (devtools/inspector.css)
|
||||
skin/classic/browser/devtools/profiler-stopwatch.png (devtools/profiler-stopwatch.png)
|
||||
skin/classic/browser/devtools/toolbox.css (devtools/toolbox.css)
|
||||
skin/classic/browser/devtools/tool-options.png (devtools/tool-options.png)
|
||||
skin/classic/browser/devtools/tool-webconsole.png (devtools/tool-webconsole.png)
|
||||
|
@ -4,6 +4,18 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
%endif
|
||||
|
||||
.profiler-sidebar-empty-notice {
|
||||
max-width: 176px;
|
||||
padding: 10px;
|
||||
background-color: rgb(61, 69, 76);
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.devtools-toolbar {
|
||||
min-height: 33px;
|
||||
}
|
||||
|
||||
.profiler-sidebar {
|
||||
min-width: 196px;
|
||||
}
|
||||
@ -30,14 +42,33 @@
|
||||
color: rgb(128, 195, 228);
|
||||
}
|
||||
|
||||
.devtools-toolbar {
|
||||
height: 26px;
|
||||
padding: 3px;
|
||||
#profiler-controls > toolbarbutton {
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
border-width: 0;
|
||||
-moz-border-end-width: 1px;
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
.devtools-toolbar .devtools-toolbarbutton {
|
||||
min-width: 48px;
|
||||
min-height: 0;
|
||||
font-size: 11px;
|
||||
padding: 0px 8px;
|
||||
#profiler-controls > toolbarbutton:last-of-type {
|
||||
-moz-border-end-width: 0;
|
||||
}
|
||||
|
||||
#profiler-controls {
|
||||
box-shadow: 0 1px 0 hsla(210,16%,76%,.15) inset,
|
||||
0 0 0 1px hsla(210,16%,76%,.15) inset,
|
||||
0 1px 0 hsla(210,16%,76%,.15);
|
||||
border: 1px solid hsla(210,8%,5%,.45);
|
||||
border-radius: 3px;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
#profiler-start {
|
||||
list-style-image: url("chrome://browser/skin/devtools/profiler-stopwatch.png");
|
||||
-moz-image-region: rect(0px,16px,16px,0px);
|
||||
}
|
||||
|
||||
#profiler-start[checked] {
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
@ -226,6 +226,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
|
||||
skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
|
||||
skin/classic/browser/devtools/inspector.css (devtools/inspector.css)
|
||||
skin/classic/browser/devtools/profiler-stopwatch.png (devtools/profiler-stopwatch.png)
|
||||
skin/classic/browser/devtools/toolbox.css (devtools/toolbox.css)
|
||||
skin/classic/browser/devtools/tool-options.png (devtools/tool-options.png)
|
||||
skin/classic/browser/devtools/tool-webconsole.png (devtools/tool-webconsole.png)
|
||||
@ -479,6 +480,7 @@ browser.jar:
|
||||
skin/classic/aero/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
|
||||
skin/classic/aero/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
|
||||
skin/classic/aero/browser/devtools/inspector.css (devtools/inspector.css)
|
||||
skin/classic/aero/browser/devtools/profiler-stopwatch.png (devtools/profiler-stopwatch.png)
|
||||
skin/classic/aero/browser/devtools/toolbox.css (devtools/toolbox.css)
|
||||
skin/classic/aero/browser/devtools/tool-options.png (devtools/tool-options.png)
|
||||
skin/classic/aero/browser/devtools/tool-webconsole.png (devtools/tool-webconsole.png)
|
||||
|
@ -14,6 +14,37 @@ function getCurrentTime() {
|
||||
/**
|
||||
* Creates a ProfilerActor. ProfilerActor provides remote access to the
|
||||
* built-in profiler module.
|
||||
*
|
||||
* ProfilerActor.onGetProfile returns a JavaScript object with data
|
||||
* generated by our built-in profiler moduele. It has the following
|
||||
* format:
|
||||
*
|
||||
* {
|
||||
* libs: string,
|
||||
* meta: {
|
||||
* interval: number,
|
||||
* platform: string,
|
||||
* (...)
|
||||
* },
|
||||
* threads: [
|
||||
* {
|
||||
* samples: [
|
||||
* {
|
||||
* frames: [
|
||||
* {
|
||||
* line: number,
|
||||
* location: string
|
||||
* }
|
||||
* ],
|
||||
* name: string
|
||||
* responsiveness: number (in ms)
|
||||
* time: number (nspr time)
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
*/
|
||||
function ProfilerActor(aConnection)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user