зеркало из https://github.com/mozilla/gecko-dev.git
Bug 828038 - Change profile recording UI and behavior; r=dcamp
This commit is contained in:
Родитель
ec75a4559a
Коммит
1706e0cec0
|
@ -2,10 +2,10 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
this.EXPORTED_SYMBOLS = [];
|
||||||
this.EXPORTED_SYMBOLS = [ ];
|
|
||||||
|
|
||||||
const Cu = Components.utils;
|
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/BuiltinCommands.jsm");
|
||||||
Cu.import("resource:///modules/devtools/CmdDebugger.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/CmdResize.jsm");
|
||||||
Cu.import("resource:///modules/devtools/CmdTilt.jsm");
|
Cu.import("resource:///modules/devtools/CmdTilt.jsm");
|
||||||
Cu.import("resource:///modules/devtools/CmdScratchpad.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:///modules/devtools/shared/event-emitter.js");
|
||||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
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 FORBIDDEN_IDS = new Set(["toolbox", ""]);
|
||||||
const MAX_ORDINAL = 99;
|
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, "WebConsolePanel", "resource:///modules/WebConsolePanel.jsm");
|
||||||
loader.lazyImporter(this, "DebuggerPanel", "resource:///modules/devtools/DebuggerPanel.jsm");
|
loader.lazyImporter(this, "DebuggerPanel", "resource:///modules/devtools/DebuggerPanel.jsm");
|
||||||
loader.lazyImporter(this, "StyleEditorPanel", "resource:///modules/devtools/StyleEditorPanel.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");
|
loader.lazyImporter(this, "NetMonitorPanel", "resource:///modules/devtools/NetMonitorPanel.jsm");
|
||||||
|
|
||||||
// Strings
|
// Strings
|
||||||
|
|
|
@ -12,4 +12,4 @@ include $(DEPTH)/config/autoconf.mk
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
||||||
libs::
|
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");
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -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
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#mainarea > .controlPane {
|
#mainarea {
|
||||||
font-size: 120%;
|
|
||||||
padding-top: 75px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#stopWrapper {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#profilerMessage {
|
|
||||||
color: #999;
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* De-emphasize chrome functions */
|
/* De-emphasize chrome functions */
|
||||||
|
|
|
@ -11,10 +11,6 @@ var gInstanceUID;
|
||||||
* @param string status
|
* @param string status
|
||||||
* Status to send to the parent page:
|
* Status to send to the parent page:
|
||||||
* - loaded, when page is loaded.
|
* - 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
|
* - displaysource, when user wants to display source
|
||||||
* @param object data (optional)
|
* @param object data (optional)
|
||||||
* Additional data to send to the parent page.
|
* Additional data to send to the parent page.
|
||||||
|
@ -109,22 +105,10 @@ function initUI() {
|
||||||
notifyParent("stop");
|
notifyParent("stop");
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
var controlPane = document.createElement("div");
|
var message = document.createElement("div");
|
||||||
var startProfiling = gStrings.getFormatStr("profiler.startProfiling",
|
message.className = "message";
|
||||||
["<span class='btn'></span>"]);
|
message.innerHTML = "To start profiling click the button above.";
|
||||||
var stopProfiling = gStrings.getFormatStr("profiler.stopProfiling",
|
gMainArea.appendChild(message);
|
||||||
["<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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
const Cu = Components.utils;
|
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.
|
* 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
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
const { Cu } = require("chrome");
|
||||||
this.EXPORTED_SYMBOLS = [];
|
module.exports = [];
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/devtools/gcli.jsm");
|
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",
|
loader.lazyGetter(this, "gDevTools",
|
||||||
"resource:///modules/devtools/gDevTools.jsm");
|
() => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools);
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
var Promise = require("sdk/core/promise");
|
||||||
"resource://gre/modules/devtools/Console.jsm");
|
|
||||||
|
|
||||||
var Promise = require('util/promise');
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 'profiler' command. Doesn't do anything.
|
* 'profiler' command. Doesn't do anything.
|
||||||
|
@ -64,39 +59,17 @@ gcli.addCommand({
|
||||||
name: "profiler start",
|
name: "profiler start",
|
||||||
description: gcli.lookup("profilerStartDesc"),
|
description: gcli.lookup("profilerStartDesc"),
|
||||||
returnType: "string",
|
returnType: "string",
|
||||||
|
params: [],
|
||||||
params: [
|
|
||||||
{
|
|
||||||
name: "name",
|
|
||||||
type: "string",
|
|
||||||
manual: gcli.lookup("profilerStartManual")
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
exec: function (args, context) {
|
exec: function (args, context) {
|
||||||
function start() {
|
function start() {
|
||||||
let name = args.name;
|
|
||||||
let panel = getPanel(context, "jsprofiler");
|
let panel = getPanel(context, "jsprofiler");
|
||||||
let profile = panel.getProfileByName(name) || panel.createProfile(name);
|
|
||||||
|
|
||||||
if (profile.isStarted) {
|
if (panel.recordingProfile)
|
||||||
throw gcli.lookup("profilerAlreadyStarted");
|
throw gcli.lookup("profilerAlreadyStarted2");
|
||||||
}
|
|
||||||
|
|
||||||
if (profile.isFinished) {
|
panel.toggleRecording();
|
||||||
throw gcli.lookup("profilerAlreadyFinished");
|
return gcli.lookup("profilerStarted");
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return gDevTools.showToolbox(context.environment.target, "jsprofiler")
|
return gDevTools.showToolbox(context.environment.target, "jsprofiler")
|
||||||
|
@ -111,42 +84,16 @@ gcli.addCommand({
|
||||||
name: "profiler stop",
|
name: "profiler stop",
|
||||||
description: gcli.lookup("profilerStopDesc"),
|
description: gcli.lookup("profilerStopDesc"),
|
||||||
returnType: "string",
|
returnType: "string",
|
||||||
|
params: [],
|
||||||
params: [
|
|
||||||
{
|
|
||||||
name: "name",
|
|
||||||
type: "string",
|
|
||||||
manual: gcli.lookup("profilerStopManual")
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
exec: function (args, context) {
|
exec: function (args, context) {
|
||||||
function stop() {
|
function stop() {
|
||||||
let panel = getPanel(context, "jsprofiler");
|
let panel = getPanel(context, "jsprofiler");
|
||||||
let profile = panel.getProfileByName(args.name);
|
|
||||||
|
|
||||||
if (!profile) {
|
if (!panel.recordingProfile)
|
||||||
throw gcli.lookup("profilerNotFound");
|
throw gcli.lookup("profilerNotStarted3");
|
||||||
}
|
|
||||||
|
|
||||||
if (profile.isFinished) {
|
panel.toggleRecording();
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return gDevTools.showToolbox(context.environment.target, "jsprofiler")
|
return gDevTools.showToolbox(context.environment.target, "jsprofiler")
|
|
@ -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";
|
"use strict";
|
||||||
|
|
||||||
const Cc = Components.classes;
|
var isJSM = typeof require !== "function";
|
||||||
const Ci = Components.interfaces;
|
|
||||||
const Cu = Components.utils;
|
// 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/dbg-client.jsm");
|
||||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||||
Cu.import("resource://gre/modules/AddonManager.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",
|
loader.lazyGetter(this, "gDevTools",
|
||||||
"resource:///modules/devtools/gDevTools.jsm");
|
() => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools);
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
|
loader.lazyGetter(this, "DebuggerServer",
|
||||||
"resource://gre/modules/devtools/dbg-server.jsm");
|
() => Cu.import("resource:///modules/devtools/dbg-server.jsm", {}).DebuggerServer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data structure that contains information that has
|
* Data structure that contains information that has
|
||||||
|
@ -44,7 +58,8 @@ function makeProfile(name, def={}) {
|
||||||
return {
|
return {
|
||||||
name: name,
|
name: name,
|
||||||
timeStarted: def.timeStarted,
|
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.client = target.client;
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
this.consoleProfiles = [];
|
this.consoleProfiles = [];
|
||||||
|
this.reservedNames = {};
|
||||||
|
|
||||||
addTarget(target);
|
addTarget(target);
|
||||||
|
|
||||||
|
@ -86,9 +102,16 @@ function ProfilerController(target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedData.controllers.set(target, this);
|
sharedData.controllers.set(target, this);
|
||||||
|
EventEmitter.decorate(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
ProfilerController.prototype = {
|
ProfilerController.prototype = {
|
||||||
|
target: null,
|
||||||
|
client: null,
|
||||||
|
isConnected: null,
|
||||||
|
consoleProfiles: null,
|
||||||
|
reservedNames: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a map of profile results for the current target.
|
* Return a map of profile results for the current target.
|
||||||
*
|
*
|
||||||
|
@ -109,6 +132,19 @@ ProfilerController.prototype = {
|
||||||
return profile.timeStarted !== null && profile.timeEnded === null;
|
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
|
* A listener that fires whenever console.profile or console.profileEnd
|
||||||
* is called.
|
* is called.
|
||||||
|
@ -117,26 +153,23 @@ ProfilerController.prototype = {
|
||||||
* Type of a call. Either 'profile' or 'profileEnd'.
|
* Type of a call. Either 'profile' or 'profileEnd'.
|
||||||
* @param object data
|
* @param object data
|
||||||
* Event 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 name = data.extra.name;
|
||||||
|
|
||||||
let profileStart = () => {
|
let profileStart = () => {
|
||||||
if (name && this.profiles.has(name))
|
if (name && this.profiles.has(name))
|
||||||
return;
|
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.
|
// Add profile structure to shared data.
|
||||||
this.profiles.set(profile.name, makeProfile(profile.name, {
|
let profile = makeProfile(name || this.getProfileName(), {
|
||||||
timeStarted: data.extra.currentTime
|
timeStarted: data.extra.currentTime,
|
||||||
}));
|
fromConsole: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this.profiles.set(profile.name, profile);
|
||||||
this.consoleProfiles.push(profile.name);
|
this.consoleProfiles.push(profile.name);
|
||||||
|
this.emit("profileStart", profile);
|
||||||
};
|
};
|
||||||
|
|
||||||
let profileEnd = () => {
|
let profileEnd = () => {
|
||||||
|
@ -156,8 +189,6 @@ ProfilerController.prototype = {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let profileData = data.extra.profile;
|
let profileData = data.extra.profile;
|
||||||
profile.timeEnded = data.extra.currentTime;
|
|
||||||
|
|
||||||
profileData.threads = profileData.threads.map((thread) => {
|
profileData.threads = profileData.threads.map((thread) => {
|
||||||
let samples = thread.samples.filter((sample) => {
|
let samples = thread.samples.filter((sample) => {
|
||||||
return sample.time >= profile.timeStarted;
|
return sample.time >= profile.timeStarted;
|
||||||
|
@ -166,10 +197,10 @@ ProfilerController.prototype = {
|
||||||
return { samples: samples };
|
return { samples: samples };
|
||||||
});
|
});
|
||||||
|
|
||||||
let ui = panel.getProfileByName(name);
|
profile.timeEnded = data.extra.currentTime;
|
||||||
ui.data = profileData;
|
profile.data = profileData;
|
||||||
ui.parse(profileData, () => panel.emit("parsed"));
|
|
||||||
ui.stop((name, cb) => cb());
|
this.emit("profileEnd", profile);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type === "profile")
|
if (type === "profile")
|
||||||
|
@ -217,27 +248,7 @@ ProfilerController.prototype = {
|
||||||
if (toolbox == null)
|
if (toolbox == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let panel = toolbox.getPanel("jsprofiler");
|
this.onConsoleEvent(resp.subject.action, resp.data);
|
||||||
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");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -392,3 +403,9 @@ ProfilerController.prototype = {
|
||||||
this.actor = null;
|
this.actor = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isJSM) {
|
||||||
|
var EXPORTED_SYMBOLS = ["ProfilerController"];
|
||||||
|
} else {
|
||||||
|
module.exports = ProfilerController;
|
||||||
|
}
|
|
@ -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">
|
<box flex="1" id="profiler-chrome" class="devtools-responsive-container">
|
||||||
<vbox class="profiler-sidebar">
|
<vbox class="profiler-sidebar">
|
||||||
<toolbar class="devtools-toolbar">
|
<toolbar class="devtools-toolbar">
|
||||||
<toolbarbutton id="profiler-create"
|
<hbox id="profiler-controls">
|
||||||
class="devtools-toolbarbutton"
|
<toolbarbutton id="profiler-start" class="devtools-toolbarbutton"/>
|
||||||
label="&profilerNew.label;"
|
</hbox>
|
||||||
disabled="true"/>
|
|
||||||
</toolbar>
|
</toolbar>
|
||||||
|
|
||||||
<vbox id="profiles-list" flex="1">
|
<vbox id="profiles-list" flex="1">
|
||||||
|
|
|
@ -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
|
include $(DEPTH)/config/autoconf.mk
|
||||||
|
|
||||||
MOCHITEST_BROWSER_TESTS = \
|
MOCHITEST_BROWSER_TESTS = \
|
||||||
browser_profiler_profiles.js \
|
|
||||||
browser_profiler_remote.js \
|
browser_profiler_remote.js \
|
||||||
browser_profiler_bug_834878_source_buttons.js \
|
browser_profiler_bug_834878_source_buttons.js \
|
||||||
browser_profiler_cmd.js \
|
browser_profiler_cmd.js \
|
||||||
browser_profiler_run.js \
|
browser_profiler_run.js \
|
||||||
browser_profiler_controller.js \
|
browser_profiler_controller.js \
|
||||||
browser_profiler_bug_830664_multiple_profiles.js \
|
|
||||||
browser_profiler_bug_855244_multiple_tabs.js \
|
browser_profiler_bug_855244_multiple_tabs.js \
|
||||||
browser_profiler_console_api.js \
|
browser_profiler_console_api.js \
|
||||||
browser_profiler_console_api_named.js \
|
browser_profiler_console_api_named.js \
|
||||||
browser_profiler_console_api_mixed.js \
|
browser_profiler_console_api_mixed.js \
|
||||||
browser_profiler_console_api_content.js \
|
browser_profiler_console_api_content.js \
|
||||||
|
browser_profiler_escape.js \
|
||||||
head.js \
|
head.js \
|
||||||
$(NULL)
|
$(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();
|
waitForExplicitFinish();
|
||||||
|
|
||||||
setUp(URL, function onSetUp(tab, browser, panel) {
|
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() {
|
panel.displaySource(data, function onOpen() {
|
||||||
let target = TargetFactory.forTab(tab);
|
let target = TargetFactory.forTab(tab);
|
||||||
let dbg = gDevTools.getToolbox(target).getPanel("jsdebugger");
|
let dbg = gDevTools.getToolbox(target).getPanel("jsdebugger");
|
||||||
let view = dbg.panelWin.DebuggerView;
|
let view = dbg.panelWin.DebuggerView;
|
||||||
|
|
||||||
is(view.Sources.selectedValue, data.uri, "URI is different");
|
is(view.Sources.selectedValue, data.uri, "URI is different");
|
||||||
is(view.editor.getCaretPosition().line, data.line - 1,
|
is(view.editor.getCaretPosition().line, data.line - 1,
|
||||||
"Line is different");
|
"Line is different");
|
||||||
|
|
||||||
// Test the case where script is already loaded.
|
// Test the case where script is already loaded.
|
||||||
view.editor.setCaretPosition(1);
|
view.editor.setCaretPosition(1);
|
||||||
gDevTools.showToolbox(target, "jsprofiler").then(function () {
|
gDevTools.showToolbox(target, "jsprofiler").then(function () {
|
||||||
panel.displaySource(data, function onOpenAgain() {
|
panel.displaySource(data, function onOpenAgain() {
|
||||||
is(view.editor.getCaretPosition().line, data.line - 1,
|
is(view.editor.getCaretPosition().line, data.line - 1,
|
||||||
"Line is different");
|
"Line is different");
|
||||||
tearDown(tab);
|
tearDown(tab);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
panel.createProfile();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,54 +41,41 @@ function testProfilerStart() {
|
||||||
let deferred = Promise.defer();
|
let deferred = Promise.defer();
|
||||||
|
|
||||||
gPanel.once("started", function () {
|
gPanel.once("started", function () {
|
||||||
is(gPanel.profiles.size, 2, "There are two profiles");
|
is(gPanel.profiles.size, 1, "There is a new profile");
|
||||||
ok(!gPanel.getProfileByName("Profile 1").isStarted, "Profile 1 wasn't started");
|
is(gPanel.getProfileByName("Profile 1"), gPanel.recordingProfile, "Recording profile is OK");
|
||||||
ok(gPanel.getProfileByName("Profile 2").isStarted, "Profile 2 was started");
|
ok(!gPanel.activeProfile, "There's no active profile yet");
|
||||||
cmd('profiler start "Profile 2"', "This profile has already been started");
|
cmd("profiler start", gcli.lookup("profilerAlreadyStarted2"));
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
cmd("profiler start", gcli.lookup("profilerStarting2"));
|
cmd("profiler start", gcli.lookup("profilerStarted"));
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function testProfilerList() {
|
function testProfilerList() {
|
||||||
let deferred = Promise.defer();
|
cmd("profiler list", /^.*Profile\s1\s\*.*$/);
|
||||||
|
|
||||||
cmd("profiler list", /^.*Profile\s1.*Profile\s2\s\*.*$/);
|
|
||||||
deferred.resolve();
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function testProfilerStop() {
|
function testProfilerStop() {
|
||||||
let deferred = Promise.defer();
|
let deferred = Promise.defer();
|
||||||
|
|
||||||
gPanel.once("stopped", function () {
|
gPanel.once("stopped", function () {
|
||||||
ok(!gPanel.getProfileByName("Profile 2").isStarted, "Profile 2 was stopped");
|
is(gPanel.activeProfile, gPanel.getProfileByName("Profile 1"), "Active profile is OK");
|
||||||
ok(gPanel.getProfileByName("Profile 2").isFinished, "Profile 2 was stopped");
|
ok(!gPanel.recordingProfile, "There's no recording profile");
|
||||||
cmd('profiler stop "Profile 2"', "This profile has already been completed. " +
|
cmd("profiler stop", gcli.lookup("profilerNotStarted3"));
|
||||||
"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")
|
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
cmd('profiler stop "Profile 2"', gcli.lookup("profilerStopping2"));
|
cmd("profiler stop");
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function testProfilerShow() {
|
function testProfilerShow() {
|
||||||
let deferred = Promise.defer();
|
let deferred = Promise.defer();
|
||||||
|
|
||||||
is(gPanel.getProfileByName("Profile 2").uid, gPanel.activeProfile.uid,
|
|
||||||
"Profile 2 is active");
|
|
||||||
|
|
||||||
gPanel.once("profileSwitched", function () {
|
gPanel.once("profileSwitched", function () {
|
||||||
is(gPanel.getProfileByName("Profile 1").uid, gPanel.activeProfile.uid,
|
is(gPanel.getProfileByName("Profile 1"), gPanel.activeProfile, "Profile 1 is active");
|
||||||
"Profile 1 is active");
|
cmd('profile show "invalid"', gcli.lookup("profilerNotFound"));
|
||||||
cmd('profile show "invalid"', "Profile not found");
|
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,46 +19,17 @@ function test() {
|
||||||
function testConsoleProfile(hud) {
|
function testConsoleProfile(hud) {
|
||||||
hud.jsterm.clearOutput(true);
|
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;
|
let profilesStarted = 0;
|
||||||
|
|
||||||
function profileEnd(_, uid) {
|
gPanel.once("parsed", () => {
|
||||||
let profile = gPanel.profiles.get(uid);
|
let profile = gPanel.activeProfile;
|
||||||
|
|
||||||
profile.once("started", () => {
|
is(profile.name, "Profile 1", "Profile name is OK");
|
||||||
if (++profilesStarted < 2)
|
is(gPanel.sidebar.selectedItem, gPanel.sidebar.getItemByProfile(profile), "Sidebar is OK");
|
||||||
return;
|
is(gPanel.sidebar.selectedItem.attachment.state, PROFILE_COMPLETED);
|
||||||
|
|
||||||
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);
|
|
||||||
tearDown(gTab, () => gTab = gPanel = null);
|
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() {
|
function runTests() {
|
||||||
is(getSidebarItem(1).attachment.state, PROFILE_IDLE);
|
is(getSidebarItem(1).attachment.state, PROFILE_COMPLETED);
|
||||||
is(getSidebarItem(2).attachment.state, PROFILE_COMPLETED);
|
|
||||||
|
|
||||||
gPanel.once("parsed", () => {
|
gPanel.once("parsed", () => {
|
||||||
function assertSampleAndFinish() {
|
function assertSampleAndFinish() {
|
||||||
|
|
|
@ -18,12 +18,13 @@ function test() {
|
||||||
|
|
||||||
function runTests(toolbox) {
|
function runTests(toolbox) {
|
||||||
let panel = toolbox.getPanel("jsprofiler");
|
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);
|
is(getSidebarItem(1, panel).attachment.state, PROFILE_RUNNING);
|
||||||
|
|
||||||
openConsole(gTab, (hud) => {
|
openConsole(gTab, (hud) => {
|
||||||
panel.profiles.get(1).once("stopped", () => {
|
panel.once("stopped", () => {
|
||||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_COMPLETED);
|
is(getSidebarItem(1, panel).attachment.state, PROFILE_COMPLETED);
|
||||||
tearDown(gTab, () => gTab = gPanel = null);
|
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;
|
let profilesStarted = 0;
|
||||||
|
|
||||||
function profileEnd(_, uid) {
|
function endProfile() {
|
||||||
let profile = gPanel.profiles.get(uid);
|
if (++profilesStarted < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
profile.once("started", () => {
|
gPanel.controller.off("profileStart", endProfile);
|
||||||
if (++profilesStarted < 2)
|
gPanel.controller.once("profileEnd", () => openProfiler(gTab, checkProfiles));
|
||||||
return;
|
hud.jsterm.execute("console.profileEnd('Second')");
|
||||||
|
|
||||||
gPanel.off("profileCreated", profileEnd);
|
|
||||||
gPanel.profiles.get(2).once("stopped", () => {
|
|
||||||
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('Second')");
|
||||||
hud.jsterm.execute("console.profile('Third')");
|
hud.jsterm.execute("console.profile('Third')");
|
||||||
}
|
}
|
||||||
|
@ -47,17 +40,14 @@ function testConsoleProfile(hud) {
|
||||||
function checkProfiles(toolbox) {
|
function checkProfiles(toolbox) {
|
||||||
let panel = toolbox.getPanel("jsprofiler");
|
let panel = toolbox.getPanel("jsprofiler");
|
||||||
|
|
||||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_IDLE);
|
is(getSidebarItem(1, panel).attachment.name, "Second", "Name in sidebar is OK");
|
||||||
is(getSidebarItem(2, panel).attachment.name, "Second");
|
is(getSidebarItem(1, panel).attachment.state, PROFILE_COMPLETED, "State in sidebar is OK");
|
||||||
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.
|
// Make sure we can still stop profiles via the queue pop.
|
||||||
|
|
||||||
gPanel.profiles.get(3).once("stopped", () => {
|
gPanel.controller.once("profileEnd", () => {
|
||||||
openProfiler(gTab, () => {
|
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);
|
tearDown(gTab, () => gTab = gPanel = null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 DebuggerClient = temp.DebuggerClient;
|
||||||
let debuggerSocketConnect = temp.debuggerSocketConnect;
|
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;
|
let ProfilerController = temp.ProfilerController;
|
||||||
|
|
||||||
function test() {
|
function test() {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||||
|
|
||||||
let gTab, gPanel, gAttempts = 0;
|
let gTab, gPanel;
|
||||||
|
|
||||||
function test() {
|
function test() {
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
@ -12,55 +12,104 @@ function test() {
|
||||||
gTab = tab;
|
gTab = tab;
|
||||||
gPanel = panel;
|
gPanel = panel;
|
||||||
|
|
||||||
panel.once("started", onStart);
|
function done() {
|
||||||
panel.once("parsed", onParsed);
|
tearDown(gTab, () => { gPanel = null; gTab = null; });
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ok(sample.length > 0, "We have some items displayed");
|
startRecording()
|
||||||
is(sample[0].innerHTML, "100.0%", "First percentage is 100%");
|
.then(stopRecording)
|
||||||
|
.then(startRecordingAgain)
|
||||||
tearDown(gTab, function onTearDown() {
|
.then(stopRecording)
|
||||||
gPanel = null;
|
.then(switchBackToTheFirstOne)
|
||||||
gTab = null;
|
.then(done);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
assertSample();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
# LOCALIZATION NOTE (profilerAlreadyStarted) A message that is displayed whenever
|
||||||
# an operation cannot be completed because the profile in question has already
|
# an operation cannot be completed because the profile in question has already
|
||||||
# been started.
|
# been started.
|
||||||
profilerAlreadyStarted=This profile has already been started
|
profilerAlreadyStarted2=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
|
|
||||||
|
|
||||||
# LOCALIZATION NOTE (profilerNotFound) A message that is displayed whenever
|
# LOCALIZATION NOTE (profilerNotFound) A message that is displayed whenever
|
||||||
# an operation cannot be completed because the profile in question could not be
|
# 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
|
# 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
|
# started yet. It also contains a hint to use the 'profile start' command to
|
||||||
# start the profiler.
|
# 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
|
# LOCALIZATION NOTE (profilerStarted) A very short string that indicates that
|
||||||
# we're starting the profiler.
|
# we have started recording.
|
||||||
profilerStarting2=Starting…
|
profilerStarted=Recording...
|
||||||
|
|
||||||
# LOCALIZATION NOTE (profilerStopping) A very short string that indicates that
|
|
||||||
# we're stopping the profiler.
|
|
||||||
profilerStopping2=Stopping…
|
|
||||||
|
|
||||||
# LOCALIZATION NOTE (profilerNotReady) A message that is displayed whenever
|
# LOCALIZATION NOTE (profilerNotReady) A message that is displayed whenever
|
||||||
# an operation cannot be completed because the profiler has not been opened yet.
|
# 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
|
<!-- LOCALIZATION NOTE (profilerNew.label): This is the label for the
|
||||||
- button that creates a new profile. -->
|
- button that creates a new profile. -->
|
||||||
<!ENTITY profilerNew.label "New">
|
<!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
|
# This string is used to show that the profile in question is in COMPLETED
|
||||||
# state meaning that it has been started and stopped already.
|
# state meaning that it has been started and stopped already.
|
||||||
profiler.stateCompleted=Completed
|
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.css (devtools/floating-scrollbars.css)
|
||||||
skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.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/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/toolbox.css (devtools/toolbox.css)
|
||||||
skin/classic/browser/devtools/tool-options.png (devtools/tool-options.png)
|
skin/classic/browser/devtools/tool-options.png (devtools/tool-options.png)
|
||||||
skin/classic/browser/devtools/tool-webconsole.png (devtools/tool-webconsole.png)
|
skin/classic/browser/devtools/tool-webconsole.png (devtools/tool-webconsole.png)
|
||||||
|
|
|
@ -286,6 +286,7 @@ browser.jar:
|
||||||
skin/classic/browser/devtools/dock-bottom.png (devtools/dock-bottom.png)
|
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/dock-side.png (devtools/dock-side.png)
|
||||||
* skin/classic/browser/devtools/inspector.css (devtools/inspector.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/toolbox.css (devtools/toolbox.css)
|
||||||
skin/classic/browser/devtools/tool-options.png (devtools/tool-options.png)
|
skin/classic/browser/devtools/tool-options.png (devtools/tool-options.png)
|
||||||
skin/classic/browser/devtools/tool-webconsole.png (devtools/tool-webconsole.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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
%endif
|
%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 {
|
.profiler-sidebar {
|
||||||
min-width: 196px;
|
min-width: 196px;
|
||||||
}
|
}
|
||||||
|
@ -30,14 +42,33 @@
|
||||||
color: rgb(128, 195, 228);
|
color: rgb(128, 195, 228);
|
||||||
}
|
}
|
||||||
|
|
||||||
.devtools-toolbar {
|
#profiler-controls > toolbarbutton {
|
||||||
height: 26px;
|
margin: 0;
|
||||||
padding: 3px;
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
border-width: 0;
|
||||||
|
-moz-border-end-width: 1px;
|
||||||
|
outline-offset: -3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.devtools-toolbar .devtools-toolbarbutton {
|
#profiler-controls > toolbarbutton:last-of-type {
|
||||||
min-width: 48px;
|
-moz-border-end-width: 0;
|
||||||
min-height: 0;
|
}
|
||||||
font-size: 11px;
|
|
||||||
padding: 0px 8px;
|
#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.css (devtools/floating-scrollbars.css)
|
||||||
skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.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/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/toolbox.css (devtools/toolbox.css)
|
||||||
skin/classic/browser/devtools/tool-options.png (devtools/tool-options.png)
|
skin/classic/browser/devtools/tool-options.png (devtools/tool-options.png)
|
||||||
skin/classic/browser/devtools/tool-webconsole.png (devtools/tool-webconsole.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.css (devtools/floating-scrollbars.css)
|
||||||
skin/classic/aero/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.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/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/toolbox.css (devtools/toolbox.css)
|
||||||
skin/classic/aero/browser/devtools/tool-options.png (devtools/tool-options.png)
|
skin/classic/aero/browser/devtools/tool-options.png (devtools/tool-options.png)
|
||||||
skin/classic/aero/browser/devtools/tool-webconsole.png (devtools/tool-webconsole.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
|
* Creates a ProfilerActor. ProfilerActor provides remote access to the
|
||||||
* built-in profiler module.
|
* 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)
|
function ProfilerActor(aConnection)
|
||||||
{
|
{
|
||||||
|
|
Загрузка…
Ссылка в новой задаче