Bug 850145 - Use SideMenuWidget in the Profiler; r=vporof

This commit is contained in:
Anton Kovalyov 2013-06-18 14:42:36 -07:00
Родитель f68b453f7a
Коммит 3cfd9dc53d
12 изменённых файлов: 223 добавлений и 178 удалений

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

@ -10,6 +10,8 @@ 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");
@ -24,6 +26,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
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
@ -159,18 +165,6 @@ ProfileUI.prototype = {
});
},
/**
* Update profile's label in the sidebar.
*
* @param string text
* New text for the label.
*/
updateLabel: function PUI_udpateLabel(text) {
let doc = this.panel.document;
let label = doc.querySelector("li#profile-" + this.uid + "> h1");
label.textContent = text;
},
/**
* Start profiling and, once started, notify the underlying page
* so that it could update the UI. Also, once started, we add a
@ -192,7 +186,7 @@ ProfileUI.prototype = {
startFn = startFn || this.panel.startProfiling.bind(this.panel);
startFn(this.name, () => {
this.isStarted = true;
this.updateLabel(this.name + " *");
this.panel.sidebar.setProfileState(this, PROFILE_RUNNING);
this.panel.broadcast(this.uid, {task: "onStarted"}); // Do we really need this?
this.emit("started");
});
@ -219,7 +213,7 @@ ProfileUI.prototype = {
stopFn(this.name, () => {
this.isStarted = false;
this.isFinished = true;
this.updateLabel(this.name);
this.panel.sidebar.setProfileState(this, PROFILE_COMPLETED);
this.panel.broadcast(this.uid, {task: "onStopped"});
this.emit("stopped");
});
@ -274,6 +268,39 @@ ProfileUI.prototype = {
}
};
function SidebarView(el) {
EventEmitter.decorate(this);
this.node = new SideMenuWidget(el);
}
ViewHelpers.create({ constructor: SidebarView, proto: MenuContainer.prototype }, {
getItemByProfile: function (profile) {
return this.orderedItems.filter((item) => item.attachment.uid === profile.uid)[0];
},
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).
@ -320,6 +347,7 @@ ProfilerPanel.prototype = {
target: null,
controller: null,
profiles: null,
sidebar: null,
_uid: null,
_activeUid: null,
@ -332,7 +360,14 @@ ProfilerPanel.prototype = {
},
set activeProfile(profile) {
if (this._activeUid === profile.uid)
return;
if (this.activeProfile)
this.activeProfile.hide();
this._activeUid = profile.uid;
profile.show();
},
get browserWindow() {
@ -357,42 +392,59 @@ ProfilerPanel.prototype = {
* @return Promise
*/
open: function PP_open() {
let promise;
// Local profiling needs to make the target remote.
if (!this.target.isRemote) {
promise = this.target.makeRemote();
} else {
promise = Promise.resolve(this.target);
}
let target = this.target;
let promise = !target.isRemote ? target.makeRemote() : Promise.resolve(target);
return promise
.then(function(target) {
.then((target) => {
let deferred = Promise.defer();
this.controller = new ProfilerController(this.target);
this.controller.connect(function onConnect() {
this.controller = new ProfilerController(this.target);
this.sidebar = new SidebarView(this.document.querySelector("#profiles-list"));
this.sidebar.node.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", function (ev) {
this.createProfile()
}.bind(this), false);
create.addEventListener("click", () => this.createProfile(), false);
create.removeAttribute("disabled");
let profile = this.createProfile();
this.switchToProfile(profile, function () {
let onSwitch = (_, uid) => {
if (profile.uid !== uid)
return;
this.off("profileSwitched", onSwitch);
this.isReady = true;
this.emit("ready");
deferred.resolve(this);
}.bind(this))
}.bind(this));
};
this.on("profileSwitched", onSwitch);
this.sidebar.selectedItem = this.sidebar.getItemByProfile(profile);
});
return deferred.promise;
}.bind(this))
.then(null, function onError(reason) {
Cu.reportError("ProfilerPanel open failed. " +
reason.error + ": " + reason.message);
});
})
.then(null, (reason) =>
Cu.reportError("ProfilePanel open failed: " + reason.message));
},
/**
@ -427,22 +479,17 @@ ProfilerPanel.prototype = {
}
}
let list = this.document.getElementById("profiles-list");
let item = this.document.createElement("li");
let wrap = this.document.createElement("h1");
name = 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);
item.setAttribute("id", "profile-" + uid);
item.setAttribute("data-uid", uid);
item.addEventListener("click", function (ev) {
this.switchToProfile(this.profiles.get(uid));
}.bind(this), false);
wrap.className = "profile-name";
wrap.textContent = name;
item.appendChild(wrap);
list.appendChild(item);
this.sidebar.push(box, { attachment: { uid: uid, name: name, state: PROFILE_IDLE } });
let profile = new ProfileUI(uid, name, this);
this.profiles.set(uid, profile);
@ -451,47 +498,6 @@ ProfilerPanel.prototype = {
return profile;
},
/**
* Switches to a different profile by making its instance an
* active one.
*
* @param ProfileUI profile
* A profile instance to switch to.
* @param function onLoad
* A function to call when profile instance is ready.
* If the instance is already loaded, onLoad will be
* called synchronously.
*/
switchToProfile: function PP_switchToProfile(profile, onLoad=function() {}) {
let doc = this.document;
if (this.activeProfile) {
this.activeProfile.hide();
}
let active = doc.querySelector("#profiles-list > li.splitview-active");
if (active) {
active.className = "";
}
doc.getElementById("profile-" + profile.uid).className = "splitview-active";
profile.show();
this.activeProfile = profile;
if (profile.isReady) {
profile.flushMessages();
this.emit("profileSwitched", profile.uid);
onLoad();
return;
}
profile.once("ready", () => {
profile.flushMessages();
this.emit("profileSwitched", profile.uid);
onLoad();
});
},
/**
* Start collecting profile data.
*

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

@ -83,7 +83,15 @@ gcli.addCommand({
throw gcli.lookup("profilerAlreadyFinished");
}
panel.switchToProfile(profile, function () profile.start());
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");
}
@ -124,7 +132,15 @@ gcli.addCommand({
throw gcli.lookup("profilerNotStarted2");
}
panel.switchToProfile(profile, function () profile.stop());
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");
}
@ -193,7 +209,7 @@ gcli.addCommand({
throw gcli.lookup("profilerNotFound");
}
panel.switchToProfile(profile);
panel.sidebar.selectedItem = panel.sidebar.getItemByProfile(profile);
}
});

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

@ -6,9 +6,9 @@
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/splitview.css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/profiler.css"?>
<?xml-stylesheet href="chrome://browser/content/devtools/splitview.css"?>
<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css"?>
<!DOCTYPE window [
<!ENTITY % profilerDTD SYSTEM "chrome://browser/locale/devtools/profiler.dtd">
@ -16,37 +16,31 @@
]>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<box flex="1" id="profiler-chrome" class="splitview-root">
<box class="splitview-controller" width="180px">
<box class="splitview-main"></box>
<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"/>
</toolbar>
<box class="splitview-nav-container">
<ol class="splitview-nav" id="profiles-list">
<!-- Example:
<li class="splitview-active" id="profile-1" data-uid="1">
<h1 class="profile-name">Profile 1</h1>
</li>
-->
</ol>
<vbox id="profiles-list" flex="1">
</vbox>
</vbox>
<spacer flex="1"/>
<splitter class="devtools-side-splitter"/>
<toolbar class="devtools-toolbar" mode="full">
<toolbarbutton id="profiler-create"
class="devtools-toolbarbutton"
label="&profilerNew.label;"
disabled="true"/>
</toolbar>
</box> <!-- splitview-nav-container -->
</box> <!-- splitview-controller -->
<vbox flex="1">
<toolbar class="devtools-toolbar">
</toolbar>
<box flex="1">
<vbox flex="1" id="profiler-report">
<!-- Example:
<iframe id="profiler-cleo-1"
src="devtools/cleopatra.html" flex="1"></iframe>
-->
</vbox>
</box>
</vbox>
</box>
</window>

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

@ -26,13 +26,6 @@ function test() {
});
}
function getCleoControls(doc) {
return [
doc.querySelector("#startWrapper button"),
doc.querySelector("#profilerMessage")
];
}
function startProfiling() {
gPanel.profiles.get(gPanel.activeProfile.uid).once("started", function () {
setTimeout(function () {
@ -40,36 +33,27 @@ function startProfiling() {
gPanel.profiles.get(2).once("started", function () setTimeout(stopProfiling, 50));
}, 50);
});
sendFromProfile(gPanel.activeProfile.uid, "start");
}
function stopProfiling() {
let [win, doc] = getProfileInternals(gUid);
let [btn, msg] = getCleoControls(doc);
is(gPanel.document.querySelector("li#profile-1 > h1").textContent,
"Profile 1 *", "Profile 1 has a star next to it.");
is(gPanel.document.querySelector("li#profile-2 > h1").textContent,
"Profile 2 *", "Profile 2 has a star next to it.");
is(getSidebarItem(1).attachment.state, PROFILE_RUNNING);
is(getSidebarItem(2).attachment.state, PROFILE_RUNNING);
gPanel.profiles.get(gPanel.activeProfile.uid).once("stopped", function () {
is(gPanel.document.querySelector("li#profile-1 > h1").textContent,
"Profile 1", "Profile 1 doesn't have a star next to it anymore.");
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) {
let [win, doc] = getProfileInternals(gUid);
let [btn, msg] = getCleoControls(doc);
is(gPanel.document.querySelector("li#profile-1 > h1").textContent,
"Profile 1", "Profile 1 doesn't have a star next to it.");
is(gPanel.document.querySelector("li#profile-2 > h1").textContent,
"Profile 2", "Profile 2 doesn't have a star next to it.");
is(getSidebarItem(1).attachment.state, PROFILE_COMPLETED);
is(getSidebarItem(2).attachment.state, PROFILE_COMPLETED);
tearDown(gTab, function onTearDown() {
gPanel = null;

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

@ -48,17 +48,15 @@ function testConsoleProfile(hud) {
function checkProfiles(toolbox) {
let panel = toolbox.getPanel("jsprofiler");
let getTitle = (uid) =>
panel.document.querySelector("li#profile-" + uid + " > h1").textContent;
is(getTitle(1), "Profile 1", "Profile 1 doesn't have a star next to it.");
is(getTitle(2), "Profile 2 *", "Profile 2 doesn't have a star next to it.");
is(getTitle(3), "Profile 3", "Profile 3 doesn't have a star next to it.");
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(getTitle(2), "Profile 2", "Profile 2 doesn't have a star next to it.");
is(getSidebarItem(2, panel).attachment.state, PROFILE_COMPLETED);
tearDown(gTab, () => gTab = gPanel = null);
});

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

@ -17,18 +17,20 @@ function test() {
openProfiler(tab, (toolbox) => {
gToolbox = toolbox;
loadUrl(PAGE, tab, () => {
gPanel.on("profileCreated", runTests);
gPanel.sidebar.on("stateChanged", (_, item) => {
if (item.attachment.state !== PROFILE_COMPLETED)
return;
runTests();
});
});
});
});
}
function runTests() {
let getTitle = (uid) =>
gPanel.document.querySelector("li#profile-" + uid + " > h1").textContent;
is(getTitle(1), "Profile 1", "Profile 1 doesn't have a star next to it.");
is(getTitle(2), "Profile 2", "Profile 2 doesn't have a star next to it.");
is(getSidebarItem(1).attachment.state, PROFILE_IDLE);
is(getSidebarItem(2).attachment.state, PROFILE_COMPLETED);
gPanel.once("parsed", () => {
function assertSampleAndFinish() {
@ -49,5 +51,6 @@ function runTests() {
assertSampleAndFinish();
});
gPanel.switchToProfile(gPanel.profiles.get(2));
let profile = gPanel.profiles.get(2);
gPanel.sidebar.selectedItem = gPanel.sidebar.getItemByProfile(profile);
}

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

@ -18,15 +18,13 @@ function test() {
function runTests(toolbox) {
let panel = toolbox.getPanel("jsprofiler");
let getTitle = (uid) =>
panel.document.querySelector("li#profile-" + uid + " > h1").textContent;
panel.profiles.get(1).once("started", () => {
is(getTitle(1), "Profile 1 *", "Profile 1 has a start next to it.");
is(getSidebarItem(1, panel).attachment.state, PROFILE_RUNNING);
openConsole(gTab, (hud) => {
panel.profiles.get(1).once("stopped", () => {
is(getTitle(1), "Profile 1", "Profile 1 doesn't have a star next to it.");
is(getSidebarItem(1, panel).attachment.state, PROFILE_COMPLETED);
tearDown(gTab, () => gTab = gPanel = null);
});

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

@ -46,18 +46,18 @@ function testConsoleProfile(hud) {
function checkProfiles(toolbox) {
let panel = toolbox.getPanel("jsprofiler");
let getTitle = (uid) =>
panel.document.querySelector("li#profile-" + uid + " > h1").textContent;
is(getTitle(1), "Profile 1", "Profile 1 doesn't have a star next to it.");
is(getTitle(2), "Second", "Second doesn't have a star next to it.");
is(getTitle(3), "Third *", "Third does have a star next to it.");
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);
// Make sure we can still stop profiles via the queue pop.
gPanel.profiles.get(3).once("stopped", () => {
openProfiler(gTab, () => {
is(getTitle(3), "Third", "Third doesn't have a star next to it.");
is(getSidebarItem(3, panel).attachment.state, PROFILE_COMPLETED);
tearDown(gTab, () => gTab = gPanel = null);
});
});

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

@ -48,8 +48,11 @@ 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 label = gPanel.document.querySelector("li#profile-" + uid + "> h1");
is(label.textContent, "Custom Profile", "Name is correct on the label");
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");

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

@ -2,8 +2,12 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
let temp = {};
const PROFILER_ENABLED = "devtools.profiler.enabled";
const REMOTE_ENABLED = "devtools.debugger.remote-enabled";
const PROFILE_IDLE = 0;
const PROFILE_RUNNING = 1;
const PROFILE_COMPLETED = 2;
Cu.import("resource:///modules/devtools/gDevTools.jsm", temp);
let gDevTools = temp.gDevTools;
@ -36,6 +40,11 @@ function getProfileInternals(uid) {
return [win, doc];
}
function getSidebarItem(uid, panel=gPanel) {
let profile = panel.profiles.get(uid);
return panel.sidebar.getItemByProfile(profile);
}
function sendFromProfile(uid, msg) {
let [win, doc] = getProfileInternals(uid);
win.parent.postMessage({ uid: uid, status: msg }, "*");

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

@ -82,3 +82,18 @@ profiler.loading=Loading profile…
# This string is displayed in the profiler whenever there is already
# another running profile. Users can run only one profile at a time.
profiler.alreadyRunning=Profiler is already running. If you want to run this profile stop Profile %S first.
# LOCALIZATION NOTE (profiler.stateIdle)
# This string is used to show that the profile in question is in IDLE
# state meaning that it hasn't been started yet.
profiler.stateIdle=Idle
# LOCALIZATION NOTE (profiler.stateRunning)
# This string is used to show that the profile in question is in RUNNING
# state meaning that it has been started and currently gathering profile data.
profiler.stateRunning=Running
# LOCALIZATION NOTE (profiler.stateCompleted)
# 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

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

@ -4,21 +4,40 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
%endif
.profile-name {
.profiler-sidebar {
min-width: 196px;
}
.profiler-sidebar + .devtools-side-splitter {
-moz-border-start-color: transparent;
}
.profiler-sidebar-item {
padding: 3px 5px;
}
.profiler-sidebar-item > h3 {
font-size: 13px;
padding: 8px;
display: block;
}
#profiles-list > li {
width: 180px;
cursor: pointer;
.profiler-sidebar-item > span {
margin-top: 2px;
color: rgb(140, 152, 165);
}
.splitview-nav-container button {
color: white;
background-clip: padding-box;
border-bottom: 1px solid hsla(210,16%,76%,.1);
box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25);
-moz-padding-end: 8px;
-moz-box-align: center;
.selected .profiler-sidebar-item > span {
color: rgb(128, 195, 228);
}
.devtools-toolbar {
height: 26px;
padding: 3px;
}
.devtools-toolbar .devtools-toolbarbutton {
min-width: 48px;
min-height: 0;
font-size: 11px;
padding: 0px 8px;
}