mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 12:25:53 +00:00
Merge fx-team to m-c. a=merge
CLOSED TREE
This commit is contained in:
commit
e39aaf3dc5
@ -203,6 +203,13 @@ loop.panel = (function(_, mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
_generateMailto: function() {
|
||||
return encodeURI([
|
||||
"mailto:?subject=" + __("share_email_subject") + "&",
|
||||
"body=" + __("share_email_body", {callUrl: this.state.callUrl})
|
||||
].join(""));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// XXX setting elem value from a state (in the callUrl input)
|
||||
// makes it immutable ie read only but that is fine in our case.
|
||||
@ -217,7 +224,13 @@ loop.panel = (function(_, mozL10n) {
|
||||
PanelLayout({summary: __("share_link_header_text")},
|
||||
React.DOM.div({className: "invite"},
|
||||
React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true",
|
||||
className: cx(inputCSSClass)})
|
||||
className: cx(inputCSSClass)}),
|
||||
React.DOM.a({className: cx({btn: true, hide: !this.state.callUrl}),
|
||||
href: this._generateMailto()},
|
||||
React.DOM.span(null,
|
||||
__("share_button")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -203,6 +203,13 @@ loop.panel = (function(_, mozL10n) {
|
||||
}
|
||||
},
|
||||
|
||||
_generateMailto: function() {
|
||||
return encodeURI([
|
||||
"mailto:?subject=" + __("share_email_subject") + "&",
|
||||
"body=" + __("share_email_body", {callUrl: this.state.callUrl})
|
||||
].join(""));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// XXX setting elem value from a state (in the callUrl input)
|
||||
// makes it immutable ie read only but that is fine in our case.
|
||||
@ -218,6 +225,12 @@ loop.panel = (function(_, mozL10n) {
|
||||
<div className="invite">
|
||||
<input type="url" value={this.state.callUrl} readOnly="true"
|
||||
className={cx(inputCSSClass)} />
|
||||
<a className={cx({btn: true, hide: !this.state.callUrl})}
|
||||
href={this._generateMailto()}>
|
||||
<span>
|
||||
{__("share_button")}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</PanelLayout>
|
||||
);
|
||||
|
@ -58,7 +58,13 @@ h1, h2, h3 {
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
/**
|
||||
* Force the display: none as it can conflict with other display.
|
||||
* You usually want to avoid !important statements as much as
|
||||
* possible. In this case, it makes sense as it's unlikely we want a
|
||||
* class to undo the hide feature.
|
||||
*/
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
|
@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.component-spacer {
|
||||
padding: 5px 10px;
|
||||
padding: 5px 10px 10px 10px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
@ -75,6 +75,28 @@
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.share .action .btn {
|
||||
background-color: #0096DD;
|
||||
border: 1px solid #0095DD;
|
||||
color: #fff;
|
||||
width: 50%;
|
||||
height: 26px;
|
||||
text-align: center;
|
||||
font-family: 'Lucida Grande', sans-serif;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.share .action .btn:hover {
|
||||
background-color: #008ACB;
|
||||
border: 1px solid #008ACB;
|
||||
}
|
||||
|
||||
.share .action .btn span {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Specific cases */
|
||||
|
||||
.panel #messages .alert {
|
||||
|
@ -247,6 +247,21 @@ describe("loop.panel", function() {
|
||||
|
||||
describe("Rendering the component should generate a call URL", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
document.mozL10n.initialize({
|
||||
getStrings: function(key) {
|
||||
var text;
|
||||
|
||||
if (key === "share_email_subject")
|
||||
text = "email-subject";
|
||||
else if (key === "share_email_body")
|
||||
text = "{{callUrl}}";
|
||||
|
||||
return JSON.stringify({textContent: text});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should make a request to requestCallUrl", function() {
|
||||
sandbox.stub(fakeClient, "requestCallUrl");
|
||||
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||
@ -290,6 +305,20 @@ describe("loop.panel", function() {
|
||||
sinon.assert.calledOnce(view.props.notifier.clear);
|
||||
});
|
||||
|
||||
it("should display a share button for email", function() {
|
||||
fakeClient.requestCallUrl = sandbox.stub();
|
||||
var mailto = 'mailto:?subject=email-subject&body=http://example.com';
|
||||
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||
notifier: notifier,
|
||||
client: fakeClient
|
||||
}));
|
||||
view.setState({pending: false, callUrl: "http://example.com"});
|
||||
|
||||
TestUtils.findRenderedDOMComponentWithTag(view, "a");
|
||||
var shareButton = view.getDOMNode().querySelector("a.btn");
|
||||
expect(shareButton.href).to.equal(encodeURI(mailto));
|
||||
});
|
||||
|
||||
it("should notify the user when the operation failed", function() {
|
||||
fakeClient.requestCallUrl = function(_, cb) {
|
||||
cb("fake error");
|
||||
|
@ -894,24 +894,13 @@ let gDevToolsBrowser = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Connects to the SPS profiler when the developer tools are open.
|
||||
* Connects to the SPS profiler when the developer tools are open. This is
|
||||
* necessary because of the WebConsole's `profile` and `profileEnd` methods.
|
||||
*/
|
||||
_connectToProfiler: function DT_connectToProfiler() {
|
||||
let ProfilerController = devtools.require("devtools/profiler/controller");
|
||||
|
||||
for (let win of gDevToolsBrowser._trackedBrowserWindows) {
|
||||
if (devtools.TargetFactory.isKnownTab(win.gBrowser.selectedTab)) {
|
||||
let target = devtools.TargetFactory.forTab(win.gBrowser.selectedTab);
|
||||
if (gDevTools._toolboxes.has(target)) {
|
||||
target.makeRemote().then(() => {
|
||||
let profiler = new ProfilerController(target);
|
||||
profiler.connect();
|
||||
}).then(null, Cu.reportError);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
_connectToProfiler: function DT_connectToProfiler(event, toolbox) {
|
||||
let SharedProfilerUtils = devtools.require("devtools/profiler/shared");
|
||||
let connection = SharedProfilerUtils.getProfilerConnection(toolbox);
|
||||
connection.open();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -78,21 +78,9 @@ browser.jar:
|
||||
content/browser/devtools/webaudioeditor-controller.js (webaudioeditor/webaudioeditor-controller.js)
|
||||
content/browser/devtools/webaudioeditor-view.js (webaudioeditor/webaudioeditor-view.js)
|
||||
content/browser/devtools/profiler.xul (profiler/profiler.xul)
|
||||
content/browser/devtools/cleopatra.html (profiler/cleopatra/cleopatra.html)
|
||||
content/browser/devtools/profiler/cleopatra/css/ui.css (profiler/cleopatra/css/ui.css)
|
||||
content/browser/devtools/profiler/cleopatra/css/tree.css (profiler/cleopatra/css/tree.css)
|
||||
content/browser/devtools/profiler/cleopatra/css/devtools.css (profiler/cleopatra/css/devtools.css)
|
||||
content/browser/devtools/profiler/cleopatra/js/strings.js (profiler/cleopatra/js/strings.js)
|
||||
content/browser/devtools/profiler/cleopatra/js/parser.js (profiler/cleopatra/js/parser.js)
|
||||
content/browser/devtools/profiler/cleopatra/js/parserWorker.js (profiler/cleopatra/js/parserWorker.js)
|
||||
content/browser/devtools/profiler/cleopatra/js/tree.js (profiler/cleopatra/js/tree.js)
|
||||
content/browser/devtools/profiler/cleopatra/js/ui.js (profiler/cleopatra/js/ui.js)
|
||||
content/browser/devtools/profiler/cleopatra/js/ProgressReporter.js (profiler/cleopatra/js/ProgressReporter.js)
|
||||
content/browser/devtools/profiler/cleopatra/js/devtools.js (profiler/cleopatra/js/devtools.js)
|
||||
content/browser/devtools/profiler/cleopatra/images/circlearrow.svg (profiler/cleopatra/images/circlearrow.svg)
|
||||
content/browser/devtools/profiler/cleopatra/images/noise.png (profiler/cleopatra/images/noise.png)
|
||||
content/browser/devtools/profiler/cleopatra/images/throbber.svg (profiler/cleopatra/images/throbber.svg)
|
||||
content/browser/devtools/profiler/cleopatra/images/treetwisty.svg (profiler/cleopatra/images/treetwisty.svg)
|
||||
content/browser/devtools/profiler.js (profiler/profiler.js)
|
||||
content/browser/devtools/ui-recordings.js (profiler/ui-recordings.js)
|
||||
content/browser/devtools/ui-profile.js (profiler/ui-profile.js)
|
||||
content/browser/devtools/responsivedesign/resize-commands.js (responsivedesign/resize-commands.js)
|
||||
content/browser/devtools/commandline.css (commandline/commandline.css)
|
||||
content/browser/devtools/commandlineoutput.xhtml (commandline/commandlineoutput.xhtml)
|
||||
|
@ -30,7 +30,7 @@ loader.lazyGetter(this, "StyleEditorPanel", () => require("devtools/styleeditor/
|
||||
loader.lazyGetter(this, "ShaderEditorPanel", () => require("devtools/shadereditor/panel").ShaderEditorPanel);
|
||||
loader.lazyGetter(this, "CanvasDebuggerPanel", () => require("devtools/canvasdebugger/panel").CanvasDebuggerPanel);
|
||||
loader.lazyGetter(this, "WebAudioEditorPanel", () => require("devtools/webaudioeditor/panel").WebAudioEditorPanel);
|
||||
loader.lazyGetter(this, "ProfilerPanel", () => require("devtools/profiler/panel"));
|
||||
loader.lazyGetter(this, "ProfilerPanel", () => require("devtools/profiler/panel").ProfilerPanel);
|
||||
loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/netmonitor/panel").NetMonitorPanel);
|
||||
loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/scratchpad/scratchpad-panel").ScratchpadPanel);
|
||||
|
||||
@ -264,18 +264,17 @@ Tools.webAudioEditor = {
|
||||
Tools.jsprofiler = {
|
||||
id: "jsprofiler",
|
||||
accesskey: l10n("profiler.accesskey", profilerStrings),
|
||||
key: l10n("profiler2.commandkey", profilerStrings),
|
||||
key: l10n("profiler.commandkey2", profilerStrings),
|
||||
ordinal: 7,
|
||||
modifiers: "shift",
|
||||
visibilityswitch: "devtools.profiler.enabled",
|
||||
icon: "chrome://browser/skin/devtools/tool-profiler.svg",
|
||||
invertIconForLightTheme: true,
|
||||
url: "chrome://browser/content/devtools/profiler.xul",
|
||||
label: l10n("profiler.label", profilerStrings),
|
||||
panelLabel: l10n("profiler.panelLabel", profilerStrings),
|
||||
label: l10n("profiler.label2", profilerStrings),
|
||||
panelLabel: l10n("profiler.panelLabel2", profilerStrings),
|
||||
tooltip: l10n("profiler.tooltip2", profilerStrings),
|
||||
inMenu: true,
|
||||
commands: "devtools/profiler/commands",
|
||||
|
||||
isTargetSupported: function (target) {
|
||||
return !target.isAddon;
|
||||
|
@ -1,167 +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";
|
||||
|
||||
let { Cu } = require("chrome");
|
||||
let { defer } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||
let EventEmitter = require("devtools/toolkit/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(panel, opts) {
|
||||
let doc = panel.document;
|
||||
let win = panel.window;
|
||||
let { uid, name } = opts;
|
||||
let spd = opts.showPlatformData;
|
||||
let ext = opts.external;
|
||||
|
||||
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=" + uid + "&spd=" + spd + "&ext=" + ext);
|
||||
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", (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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Firefox Profiler (SPS)</title>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="profiler/cleopatra/css/ui.css">
|
||||
<link rel="stylesheet" type="text/css" href="profiler/cleopatra/css/tree.css">
|
||||
<link rel="stylesheet" type="text/css" href="profiler/cleopatra/css/devtools.css">
|
||||
|
||||
<script src="profiler/cleopatra/js/strings.js"></script>
|
||||
<script src="profiler/cleopatra/js/parser.js"></script>
|
||||
<script src="profiler/cleopatra/js/tree.js"></script>
|
||||
<script src="profiler/cleopatra/js/ui.js"></script>
|
||||
<script src="profiler/cleopatra/js/ProgressReporter.js"></script>
|
||||
<script src="profiler/cleopatra/js/devtools.js"></script>
|
||||
</head>
|
||||
|
||||
<body onload="notifyParent('loaded');">
|
||||
<script>
|
||||
initUI();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,11 +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/. */
|
||||
|
||||
#mainarea {
|
||||
}
|
||||
|
||||
/* De-emphasize chrome functions */
|
||||
.resourceIcon[data-resource^=otherhost_] + .functionName {
|
||||
color: #999;
|
||||
}
|
@ -1,236 +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/. */
|
||||
|
||||
.treeViewContainer {
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
line-height: 16px;
|
||||
height: 100%;
|
||||
outline: none; /* override the browser's focus styling */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.treeHeader {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.treeColumnHeader {
|
||||
position: absolute;
|
||||
display: block;
|
||||
background: linear-gradient(#FFF 45%, #EEE 60%);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
top: 0;
|
||||
height: 15px;
|
||||
line-height: 15px;
|
||||
border: 0 solid #CCC;
|
||||
border-bottom-width: 1px;
|
||||
text-indent: 5px;
|
||||
}
|
||||
|
||||
.treeColumnHeader:not(:last-child) {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
.treeColumnHeader0 {
|
||||
left: 0;
|
||||
width: 86px;
|
||||
}
|
||||
|
||||
.treeColumnHeader1 {
|
||||
left: 99px;
|
||||
width: 35px;
|
||||
}
|
||||
|
||||
.treeColumnHeader0,
|
||||
.treeColumnHeader1 {
|
||||
text-align: right;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.treeColumnHeader2 {
|
||||
left: 147px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.treeViewVerticalScrollbox {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.treeViewNode,
|
||||
.treeViewHorizontalScrollbox {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.treeViewNode {
|
||||
min-width: -moz-min-content;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.treeViewHorizontalScrollbox {
|
||||
padding-left: 150px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.treeViewVerticalScrollbox,
|
||||
.treeViewHorizontalScrollbox {
|
||||
background: linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF);
|
||||
background-size: 100px 32px;
|
||||
}
|
||||
|
||||
.leftColumnBackground {
|
||||
background: linear-gradient(left, transparent, transparent 98px, #CCC 98px, #CCC 99px, transparent 99px),
|
||||
linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF);
|
||||
background-size: auto, 100px 32px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 146px;
|
||||
min-height: 100%;
|
||||
border-right: 1px solid #CCC;
|
||||
}
|
||||
|
||||
.sampleCount,
|
||||
.samplePercentage,
|
||||
.selfSampleCount {
|
||||
position: absolute;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.sampleCount {
|
||||
left: 2px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.samplePercentage {
|
||||
left: 55px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.selfSampleCount {
|
||||
left: 98px;
|
||||
width: 45px;
|
||||
padding-right: 2px;
|
||||
border: solid #CCC;
|
||||
border-width: 0 1px;
|
||||
}
|
||||
|
||||
.libraryName {
|
||||
margin-left: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.treeViewNode > .treeViewNodeList {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.treeViewNode.collapsed > .treeViewNodeList {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.treeLine {
|
||||
/* extend the selection background almost infinitely to the left */
|
||||
margin-left: -10000px;
|
||||
padding-left: 10000px;
|
||||
}
|
||||
|
||||
.treeLine.selected {
|
||||
color: black;
|
||||
background-color: -moz-dialog;
|
||||
}
|
||||
|
||||
.treeLine.selected > .sampleCount {
|
||||
background-color: inherit;
|
||||
margin-left: -2px;
|
||||
padding-left: 2px;
|
||||
padding-right: 95px;
|
||||
margin-right: -95px;
|
||||
}
|
||||
|
||||
.treeViewContainer:focus .treeLine.selected {
|
||||
color: highlighttext;
|
||||
background-color: highlight;
|
||||
}
|
||||
|
||||
.treeViewContainer:focus .treeLine.selected > .libraryName {
|
||||
color: #CCC;
|
||||
}
|
||||
|
||||
.expandCollapseButton,
|
||||
.focusCallstackButton {
|
||||
background: none 0 0 no-repeat transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
overflow: hidden;
|
||||
vertical-align: top;
|
||||
color: transparent;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.expandCollapseButton {
|
||||
background-image: url(../images/treetwisty.svg);
|
||||
}
|
||||
|
||||
.focusCallstackButton {
|
||||
background-image: url(../images/circlearrow.svg);
|
||||
margin-left: 5px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.expandCollapseButton:active:hover,
|
||||
.focusCallstackButton:active:hover {
|
||||
background-position: -16px 0;
|
||||
}
|
||||
|
||||
.treeViewNode.collapsed > .treeLine > .expandCollapseButton {
|
||||
background-position: 0 -16px;
|
||||
}
|
||||
|
||||
.treeViewNode.collapsed > .treeLine > .expandCollapseButton:active:hover {
|
||||
background-position: -16px -16px;
|
||||
}
|
||||
|
||||
.treeViewContainer:focus .treeLine.selected > .expandCollapseButton,
|
||||
.treeViewContainer:focus .treeLine.selected > .focusCallstackButton {
|
||||
background-position: -32px 0;
|
||||
}
|
||||
|
||||
.treeViewContainer:focus .treeViewNode.collapsed > .treeLine.selected > .expandCollapseButton {
|
||||
background-position: -32px -16px;
|
||||
}
|
||||
|
||||
.treeViewContainer:focus .treeLine.selected > .expandCollapseButton:active:hover,
|
||||
.treeViewContainer:focus .treeLine.selected > .focusCallstackButton:active:hover {
|
||||
background-position: -48px 0;
|
||||
}
|
||||
|
||||
.treeViewContainer:focus .treeViewNode.collapsed > .treeLine.selected > .expandCollapseButton:active:hover {
|
||||
background-position: -48px -16px;
|
||||
}
|
||||
|
||||
.treeViewNode.leaf > * > .expandCollapseButton {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.treeLine:hover > .focusCallstackButton {
|
||||
visibility: visible;
|
||||
}
|
@ -1,340 +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/. */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Lucida Grande", sans-serif;
|
||||
font-size: 11px;
|
||||
}
|
||||
#mainarea {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.finishedProfilePane,
|
||||
.finishedProfilePaneBackgroundCover,
|
||||
.profileEntryPane,
|
||||
.profileProgressPane {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.profileEntryPane {
|
||||
overflow: auto;
|
||||
}
|
||||
.profileEntryPane,
|
||||
.profileProgressPane {
|
||||
padding: 20px;
|
||||
background-color: rgb(229,229,229);
|
||||
background-image: url(../images/noise.png),
|
||||
linear-gradient(rgba(255,255,255,.5),rgba(255,255,255,.2));
|
||||
text-shadow: rgba(255, 255, 255, 0.4) 0 1px;
|
||||
}
|
||||
.profileEntryPane h1 {
|
||||
margin-top: 0;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.profileEntryPane input[type="file"] {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.profileProgressPane a {
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
left: 30%;
|
||||
width: 40%;
|
||||
height: 16px;
|
||||
}
|
||||
.profileProgressPane progress {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 30%;
|
||||
width: 40%;
|
||||
height: 16px;
|
||||
}
|
||||
.finishedProfilePaneBackgroundCover {
|
||||
animation: darken 300ms cubic-bezier(0, 0, 1, 0);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.finishedProfilePane {
|
||||
animation: appear 300ms ease-out;
|
||||
}
|
||||
|
||||
@keyframes darken {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes appear {
|
||||
from {
|
||||
transform: scale(0.3);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
.breadcrumbTrail {
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 29px;
|
||||
left: 0;
|
||||
background: linear-gradient(#FFF 50%, #F3F3F3 55%);
|
||||
border-bottom: 1px solid #CCC;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.breadcrumbTrailItem {
|
||||
background: linear-gradient(#FFF 50%, #F3F3F3 55%);
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
float: left;
|
||||
line-height: 29px;
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
border-right: 1px solid #CCC;
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
@keyframes slide-out {
|
||||
from {
|
||||
margin-left: -270px;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
margin-left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes slide-out {
|
||||
from {
|
||||
margin-left: -270px;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
margin-left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.breadcrumbTrailItem:not(:first-child) {
|
||||
animation: slide-out;
|
||||
animation-duration: 400ms;
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
.breadcrumbTrailItem.selected {
|
||||
background: linear-gradient(#E5E5E5 50%, #DADADA 55%);
|
||||
}
|
||||
.breadcrumbTrailItem:not(.selected):active:hover {
|
||||
background: linear-gradient(#F2F2F2 50%, #E6E6E6 55%);
|
||||
}
|
||||
.breadcrumbTrailItem.deleted {
|
||||
transition: 400ms ease-out;
|
||||
transition-property: opacity, margin-left;
|
||||
opacity: 0;
|
||||
margin-left: -270px;
|
||||
}
|
||||
.treeContainer {
|
||||
/*For asbolute position child*/
|
||||
position: relative;
|
||||
}
|
||||
.tree {
|
||||
height: 100%;
|
||||
}
|
||||
#sampleBar {
|
||||
position: absolute;
|
||||
float: right;
|
||||
left: auto;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
}
|
||||
#fileList {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 360px;
|
||||
width: 199px;
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: #DBDFE7;
|
||||
border-right: 1px solid #BBB;
|
||||
cursor: pointer;
|
||||
}
|
||||
#infoBar dl {
|
||||
margin: 0;
|
||||
}
|
||||
#infoBar dt,
|
||||
#infoBar dd {
|
||||
display: inline;
|
||||
}
|
||||
#infoBar dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
#infoBar dt::after {
|
||||
content: " ";
|
||||
white-space: pre;
|
||||
}
|
||||
#infoBar dd {
|
||||
margin-left: 0;
|
||||
}
|
||||
#infoBar dd::after {
|
||||
content: "\a";
|
||||
white-space:pre;
|
||||
}
|
||||
.sideBar {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 200px;
|
||||
height: 480px;
|
||||
overflow: auto;
|
||||
padding: 3px;
|
||||
background: #EEE;
|
||||
border-top: 1px solid #BBB;
|
||||
border-right: 1px solid #BBB;
|
||||
}
|
||||
.sideBar h2 {
|
||||
font-size: 1em;
|
||||
padding: 1px 3px;
|
||||
margin: 3px -3px;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border: solid #CCC;
|
||||
border-width: 1px 0;
|
||||
}
|
||||
.sideBar h2:first-child {
|
||||
margin-top: -4px;
|
||||
}
|
||||
.sideBar ul {
|
||||
margin: 2px 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
.pluginview {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background-color: white;
|
||||
}
|
||||
.pluginviewIFrame {
|
||||
border-style: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.histogram {
|
||||
position: relative;
|
||||
height: 60px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
border-bottom: 1px solid #CCC;
|
||||
background: linear-gradient(#EEE, #CCC);
|
||||
}
|
||||
.histogramHilite {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.histogramHilite:not(.collapsed) {
|
||||
background: rgba(150, 150, 150, 0.5);
|
||||
}
|
||||
.histogramMouseMarker {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
.histogramMouseMarker:not(.collapsed) {
|
||||
background: rgba(0, 0, 150, 0.7);
|
||||
}
|
||||
#iconbox {
|
||||
display: none;
|
||||
}
|
||||
#filter, #showall {
|
||||
cursor: pointer;
|
||||
}
|
||||
.markers {
|
||||
display: none;
|
||||
}
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
.fileListItem {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 40px;
|
||||
text-indent: 8px;
|
||||
}
|
||||
.fileListItem.selected {
|
||||
background: linear-gradient(#4B91D7 1px, #5FA9E4 1px, #5FA9E4 2px, #58A0DE 3px, #2B70C7 39px, #2763B4 39px);
|
||||
color: #FFF;
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.fileListItemTitle {
|
||||
display: block;
|
||||
padding-top: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.fileListItemDescription {
|
||||
display: block;
|
||||
line-height: 15px;
|
||||
font-size: 9px;
|
||||
}
|
||||
.busyCover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
background: rgba(120, 120, 120, 0.2);
|
||||
transition: 200ms ease-in-out;
|
||||
transition-property: visibility, opacity;
|
||||
}
|
||||
.busyCover.busy {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
.busyCover::before {
|
||||
content: url(../images/throbber.svg);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -12px;
|
||||
}
|
||||
label {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
.videoPane {
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
}
|
||||
.video {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
@ -1,27 +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/. -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="64" height="16" viewBox="0 0 64 16">
|
||||
<defs>
|
||||
<mask id="arrowInCircle" maskContentUnits="userSpaceOnUse">
|
||||
<circle cx="8" cy="8" r="6" fill="white"/>
|
||||
<rect x="4.5" y="7" width="3.5" height="2" fill="black"/>
|
||||
<polyline points="8 4 12 8 8 12" fill="black"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<g fill="#888">
|
||||
<rect x="0" y="0" width="16" height="16" mask="url(#arrowInCircle)"/>
|
||||
</g>
|
||||
<g fill="#444" transform="translate(16,0)">
|
||||
<rect x="0" y="0" width="16" height="16" mask="url(#arrowInCircle)"/>
|
||||
</g>
|
||||
<g fill="#FFF" transform="translate(32,0)">
|
||||
<rect x="0" y="0" width="16" height="16" mask="url(#arrowInCircle)"/>
|
||||
</g>
|
||||
<g fill="rgba(255, 255, 255, 0.7)" transform="translate(48,0)">
|
||||
<rect x="0" y="0" width="16" height="16" mask="url(#arrowInCircle)"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,23 +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/. -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="24" height="24" viewBox="0 0 64 64">
|
||||
<g>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(0, 32, 32)" fill="#BBB"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(30, 32, 32)" fill="#AAA"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(60, 32, 32)" fill="#999"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(90, 32, 32)" fill="#888"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(120, 32, 32)" fill="#777"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(150, 32, 32)" fill="#666"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(180, 32, 32)" fill="#555"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(210, 32, 32)" fill="#444"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(240, 32, 32)" fill="#333"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(270, 32, 32)" fill="#222"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(300, 32, 32)" fill="#111"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(330, 32, 32)" fill="#000"/>
|
||||
<animateTransform attributeName="transform" type="rotate" calcMode="discrete" values="0 32 32;30 32 32;60 32 32;90 32 32;120 32 32;150 32 32;180 32 32;210 32 32;240 32 32;270 32 32;300 32 32;330 32 32" dur="0.8s" repeatCount="indefinite"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.7 KiB |
@ -1,32 +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/. -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="64" height="32" viewBox="0 0 64 32">
|
||||
<g fill="#888">
|
||||
<polyline points="3 4 12 4 7.5 12"/>
|
||||
<g transform="translate(0,16)">
|
||||
<polyline points="3 4 12 4 7.5 12" transform="rotate(-90, 7.5, 7.5)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#444" transform="translate(16,0)">
|
||||
<polyline points="3 4 12 4 7.5 12"/>
|
||||
<g transform="translate(0,16)">
|
||||
<polyline points="3 4 12 4 7.5 12" transform="rotate(-90, 7.5, 7.5)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#FFF" transform="translate(32,0)">
|
||||
<polyline points="3 4 12 4 7.5 12"/>
|
||||
<g transform="translate(0,16)">
|
||||
<polyline points="3 4 12 4 7.5 12" transform="rotate(-90, 7.5, 7.5)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="rgba(255, 255, 255, 0.7)" transform="translate(48,0)">
|
||||
<polyline points="3 4 12 4 7.5 12"/>
|
||||
<g transform="translate(0,16)">
|
||||
<polyline points="3 4 12 4 7.5 12" transform="rotate(-90, 7.5, 7.5)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,187 +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/. */
|
||||
|
||||
/**
|
||||
* ProgressReporter
|
||||
*
|
||||
* This class is used by long-winded tasks to report progress to observers.
|
||||
* If a task has subtasks that want to report their own progress, these
|
||||
* subtasks can have their own progress reporters which are hooked up to the
|
||||
* parent progress reporter, resulting in a tree structure. A parent progress
|
||||
* reporter will calculate its progress value as a weighted sum of its
|
||||
* subreporters' progress values.
|
||||
*
|
||||
* A progress reporter has a state, an action, and a progress value.
|
||||
*
|
||||
* - state is one of STATE_WAITING, STATE_DOING and STATE_FINISHED.
|
||||
* - action is a string that describes the current task.
|
||||
* - progress is the progress value as a number between 0 and 1, or NaN if
|
||||
* indeterminate.
|
||||
*
|
||||
* A progress reporter starts out in the WAITING state. The DOING state is
|
||||
* entered with the begin method which also sets the action. While the task is
|
||||
* executing, the progress value can be updated with the setProgress method.
|
||||
* When a task has finished, it can call the finish method which is just a
|
||||
* shorthand for setProgress(1); this will set the state to FINISHED.
|
||||
*
|
||||
* Progress observers can be added with the addListener method which takes a
|
||||
* function callback. Whenever the progress value or state change, all
|
||||
* listener callbacks will be called with the progress reporter object. The
|
||||
* observer can get state, progress value and action by calling the getter
|
||||
* methods getState(), getProgress() and getAction().
|
||||
*
|
||||
* Creating child progress reporters for subtasks can be done with the
|
||||
* addSubreporter(s) methods. If a progress reporter has subreporters, normal
|
||||
* progress report functions (setProgress and finish) can no longer be called.
|
||||
* Instead, the parent reporter will listen to progress changes on its
|
||||
* subreporters and update its state automatically, and then notify its own
|
||||
* listeners.
|
||||
* When adding a subreporter, you are expected to provide an estimated
|
||||
* duration for the subtask. This value will be used as a weight when
|
||||
* calculating the progress of the parent reporter.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const gDebugExpectedDurations = false;
|
||||
|
||||
function ProgressReporter() {
|
||||
this._observers = [];
|
||||
this._subreporters = [];
|
||||
this._subreporterExpectedDurationsSum = 0;
|
||||
this._progress = 0;
|
||||
this._state = ProgressReporter.STATE_WAITING;
|
||||
this._action = "";
|
||||
}
|
||||
|
||||
ProgressReporter.STATE_WAITING = 0;
|
||||
ProgressReporter.STATE_DOING = 1;
|
||||
ProgressReporter.STATE_FINISHED = 2;
|
||||
|
||||
ProgressReporter.prototype = {
|
||||
getProgress: function () {
|
||||
return this._progress;
|
||||
},
|
||||
getState: function () {
|
||||
return this._state;
|
||||
},
|
||||
setAction: function (action) {
|
||||
this._action = action;
|
||||
this._reportProgress();
|
||||
},
|
||||
getAction: function () {
|
||||
switch (this._state) {
|
||||
case ProgressReporter.STATE_WAITING:
|
||||
return "Waiting for preceding tasks to finish...";
|
||||
case ProgressReporter.STATE_DOING:
|
||||
return this._action;
|
||||
case ProgressReporter.STATE_FINISHED:
|
||||
return "Finished.";
|
||||
default:
|
||||
throw "Broken state";
|
||||
}
|
||||
},
|
||||
addListener: function (callback) {
|
||||
this._observers.push(callback);
|
||||
},
|
||||
addSubreporter: function (expectedDuration) {
|
||||
this._subreporterExpectedDurationsSum += expectedDuration;
|
||||
var subreporter = new ProgressReporter();
|
||||
var self = this;
|
||||
subreporter.addListener(function (progress) {
|
||||
self._recalculateProgressFromSubreporters();
|
||||
self._recalculateStateAndActionFromSubreporters();
|
||||
self._reportProgress();
|
||||
});
|
||||
this._subreporters.push({ expectedDuration: expectedDuration, reporter: subreporter });
|
||||
return subreporter;
|
||||
},
|
||||
addSubreporters: function (expectedDurations) {
|
||||
var reporters = {};
|
||||
for (var key in expectedDurations) {
|
||||
reporters[key] = this.addSubreporter(expectedDurations[key]);
|
||||
}
|
||||
return reporters;
|
||||
},
|
||||
begin: function (action) {
|
||||
this._startTime = Date.now();
|
||||
this._state = ProgressReporter.STATE_DOING;
|
||||
this._action = action;
|
||||
this._reportProgress();
|
||||
},
|
||||
setProgress: function (progress) {
|
||||
if (this._subreporters.length > 0)
|
||||
throw "Can't call setProgress on a progress reporter with subreporters";
|
||||
if (progress != this._progress &&
|
||||
(progress == 1 ||
|
||||
(isNaN(progress) != isNaN(this._progress)) ||
|
||||
(progress - this._progress >= 0.01))) {
|
||||
this._progress = progress;
|
||||
if (progress == 1)
|
||||
this._transitionToFinished();
|
||||
this._reportProgress();
|
||||
}
|
||||
},
|
||||
finish: function () {
|
||||
this.setProgress(1);
|
||||
},
|
||||
_recalculateProgressFromSubreporters: function () {
|
||||
if (this._subreporters.length == 0)
|
||||
throw "Can't _recalculateProgressFromSubreporters on a progress reporter without any subreporters";
|
||||
this._progress = 0;
|
||||
for (var i = 0; i < this._subreporters.length; i++) {
|
||||
var expectedDuration = this._subreporters[i].expectedDuration;
|
||||
var reporter = this._subreporters[i].reporter;
|
||||
this._progress += reporter.getProgress() * expectedDuration / this._subreporterExpectedDurationsSum;
|
||||
}
|
||||
},
|
||||
_recalculateStateAndActionFromSubreporters: function () {
|
||||
if (this._subreporters.length == 0)
|
||||
throw "Can't _recalculateStateAndActionFromSubreporters on a progress reporter without any subreporters";
|
||||
var actions = [];
|
||||
var allWaiting = true;
|
||||
var allFinished = true;
|
||||
for (var i = 0; i < this._subreporters.length; i++) {
|
||||
var expectedDuration = this._subreporters[i].expectedDuration;
|
||||
var reporter = this._subreporters[i].reporter;
|
||||
var state = reporter.getState();
|
||||
if (state != ProgressReporter.STATE_WAITING)
|
||||
allWaiting = false;
|
||||
if (state != ProgressReporter.STATE_FINISHED)
|
||||
allFinished = false;
|
||||
if (state == ProgressReporter.STATE_DOING)
|
||||
actions.push(reporter.getAction());
|
||||
}
|
||||
if (allFinished) {
|
||||
this._transitionToFinished();
|
||||
} else if (!allWaiting) {
|
||||
this._state = ProgressReporter.STATE_DOING;
|
||||
if (actions.length == 0) {
|
||||
this._action = "About to start next task..."
|
||||
} else {
|
||||
this._action = actions.join("\n");
|
||||
}
|
||||
}
|
||||
},
|
||||
_transitionToFinished: function () {
|
||||
this._state = ProgressReporter.STATE_FINISHED;
|
||||
|
||||
if (gDebugExpectedDurations) {
|
||||
this._realDuration = Date.now() - this._startTime;
|
||||
if (this._subreporters.length) {
|
||||
for (var i = 0; i < this._subreporters.length; i++) {
|
||||
var expectedDuration = this._subreporters[i].expectedDuration;
|
||||
var reporter = this._subreporters[i].reporter;
|
||||
var realDuration = reporter._realDuration;
|
||||
dump("For reporter with expectedDuration " + expectedDuration + ", real duration was " + realDuration + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_reportProgress: function () {
|
||||
for (var i = 0; i < this._observers.length; i++) {
|
||||
this._observers[i](this);
|
||||
}
|
||||
},
|
||||
};
|
@ -1,244 +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";
|
||||
|
||||
var gInstanceUID;
|
||||
var gParsedQS;
|
||||
var gHideSourceLinks;
|
||||
|
||||
function getParam(key) {
|
||||
if (gParsedQS)
|
||||
return gParsedQS[key];
|
||||
|
||||
var query = window.location.search.substring(1);
|
||||
gParsedQS = {};
|
||||
|
||||
query.split("&").forEach(function (pair) {
|
||||
pair = pair.split("=");
|
||||
gParsedQS[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
|
||||
});
|
||||
|
||||
return gParsedQS[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the parent window with a status
|
||||
* update.
|
||||
*
|
||||
* @param string status
|
||||
* Status to send to the parent page:
|
||||
* - loaded, when page is loaded.
|
||||
* - displaysource, when user wants to display source
|
||||
* @param object data (optional)
|
||||
* Additional data to send to the parent page.
|
||||
*/
|
||||
function notifyParent(status, data={}) {
|
||||
if (!gInstanceUID) {
|
||||
gInstanceUID = getParam("uid");
|
||||
}
|
||||
|
||||
window.parent.postMessage({
|
||||
uid: gInstanceUID,
|
||||
status: status,
|
||||
data: data
|
||||
}, "*");
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener for incoming messages from the parent
|
||||
* page. All incoming messages must be stringified
|
||||
* JSON objects to be compatible with Cleopatra's
|
||||
* format:
|
||||
*
|
||||
* {
|
||||
* task: string,
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* This listener recognizes two tasks: onStarted and
|
||||
* onStopped.
|
||||
*
|
||||
* @param object event
|
||||
* PostMessage event object.
|
||||
*/
|
||||
function onParentMessage(event) {
|
||||
var start = document.getElementById("startWrapper");
|
||||
var stop = document.getElementById("stopWrapper");
|
||||
var profilerMessage = document.getElementById("profilerMessage");
|
||||
var msg = JSON.parse(event.data);
|
||||
|
||||
if (msg.task !== "receiveProfileData" && !msg.isCurrent) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.task) {
|
||||
case "onStarted":
|
||||
start.style.display = "none";
|
||||
start.querySelector("button").removeAttribute("disabled");
|
||||
stop.style.display = "inline";
|
||||
break;
|
||||
case "onStopped":
|
||||
stop.style.display = "none";
|
||||
stop.querySelector("button").removeAttribute("disabled");
|
||||
start.style.display = "inline";
|
||||
break;
|
||||
case "receiveProfileData":
|
||||
loadProfile(JSON.stringify(msg.rawProfile));
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", onParentMessage);
|
||||
|
||||
/**
|
||||
* Main entry point. This function initializes Cleopatra
|
||||
* in the light mode and creates all the UI we need.
|
||||
*/
|
||||
function initUI() {
|
||||
gHideSourceLinks = getParam("ext") === "true";
|
||||
gFileList = { profileParsingFinished: function () {} };
|
||||
gInfoBar = { display: function () {} };
|
||||
|
||||
var container = document.createElement("div");
|
||||
container.id = "ui";
|
||||
|
||||
gMainArea = document.createElement("div");
|
||||
gMainArea.id = "mainarea";
|
||||
|
||||
container.appendChild(gMainArea);
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modified copy of Cleopatra's enterFinishedProfileUI.
|
||||
* By overriding the function we don't need to modify ui.js which helps
|
||||
* with updating from upstream.
|
||||
*/
|
||||
function enterFinishedProfileUI() {
|
||||
var cover = document.createElement("div");
|
||||
cover.className = "finishedProfilePaneBackgroundCover";
|
||||
|
||||
var pane = document.createElement("table");
|
||||
var rowIndex = 0;
|
||||
var currRow;
|
||||
|
||||
pane.style.width = "100%";
|
||||
pane.style.height = "100%";
|
||||
pane.border = "0";
|
||||
pane.cellPadding = "0";
|
||||
pane.cellSpacing = "0";
|
||||
pane.borderCollapse = "collapse";
|
||||
pane.className = "finishedProfilePane";
|
||||
|
||||
gBreadcrumbTrail = new BreadcrumbTrail();
|
||||
currRow = pane.insertRow(rowIndex++);
|
||||
currRow.insertCell(0).appendChild(gBreadcrumbTrail.getContainer());
|
||||
|
||||
gHistogramView = new HistogramView();
|
||||
currRow = pane.insertRow(rowIndex++);
|
||||
currRow.insertCell(0).appendChild(gHistogramView.getContainer());
|
||||
|
||||
if (gMeta && gMeta.videoCapture) {
|
||||
gVideoPane = new VideoPane(gMeta.videoCapture);
|
||||
gVideoPane.onTimeChange(videoPaneTimeChange);
|
||||
currRow = pane.insertRow(rowIndex++);
|
||||
currRow.insertCell(0).appendChild(gVideoPane.getContainer());
|
||||
}
|
||||
|
||||
var tree = document.createElement("div");
|
||||
tree.className = "treeContainer";
|
||||
tree.style.width = "100%";
|
||||
tree.style.height = "100%";
|
||||
|
||||
gTreeManager = new ProfileTreeManager();
|
||||
gTreeManager.treeView.setColumns([
|
||||
{ name: "sampleCount", title: gStrings["Running Time"] },
|
||||
{ name: "selfSampleCount", title: gStrings["Self"] },
|
||||
{ name: "resource", title: "" }
|
||||
]);
|
||||
|
||||
currRow = pane.insertRow(rowIndex++);
|
||||
currRow.style.height = "100%";
|
||||
|
||||
var cell = currRow.insertCell(0);
|
||||
cell.appendChild(tree);
|
||||
tree.appendChild(gTreeManager.getContainer());
|
||||
|
||||
gPluginView = new PluginView();
|
||||
tree.appendChild(gPluginView.getContainer());
|
||||
|
||||
gMainArea.appendChild(cover);
|
||||
gMainArea.appendChild(pane);
|
||||
|
||||
var currentBreadcrumb = gSampleFilters;
|
||||
gBreadcrumbTrail.add({
|
||||
title: gStrings["Complete Profile"],
|
||||
enterCallback: function () {
|
||||
gSampleFilters = [];
|
||||
filtersChanged();
|
||||
}
|
||||
});
|
||||
|
||||
if (currentBreadcrumb == null || currentBreadcrumb.length == 0) {
|
||||
gTreeManager.restoreSerializedSelectionSnapshot(gRestoreSelection);
|
||||
viewOptionsChanged();
|
||||
}
|
||||
|
||||
for (var i = 0; i < currentBreadcrumb.length; i++) {
|
||||
var filter = currentBreadcrumb[i];
|
||||
var forceSelection = null;
|
||||
if (gRestoreSelection != null && i == currentBreadcrumb.length - 1) {
|
||||
forceSelection = gRestoreSelection;
|
||||
}
|
||||
switch (filter.type) {
|
||||
case "FocusedFrameSampleFilter":
|
||||
focusOnSymbol(filter.name, filter.symbolName);
|
||||
gBreadcrumbTrail.enterLastItem(forceSelection);
|
||||
case "FocusedCallstackPrefixSampleFilter":
|
||||
focusOnCallstack(filter.focusedCallstack, filter.name, false);
|
||||
gBreadcrumbTrail.enterLastItem(forceSelection);
|
||||
case "FocusedCallstackPostfixSampleFilter":
|
||||
focusOnCallstack(filter.focusedCallstack, filter.name, true);
|
||||
gBreadcrumbTrail.enterLastItem(forceSelection);
|
||||
case "RangeSampleFilter":
|
||||
gHistogramView.selectRange(filter.start, filter.end);
|
||||
gBreadcrumbTrail.enterLastItem(forceSelection);
|
||||
}
|
||||
}
|
||||
|
||||
// Show platform data?
|
||||
if (getParam("spd") !== "true")
|
||||
toggleJavascriptOnly();
|
||||
}
|
||||
|
||||
function enterProgressUI() {
|
||||
var pane = document.createElement("div");
|
||||
var label = document.createElement("a");
|
||||
var bar = document.createElement("progress");
|
||||
var string = gStrings.getStr("profiler.loading");
|
||||
|
||||
pane.className = "profileProgressPane";
|
||||
pane.appendChild(label);
|
||||
pane.appendChild(bar);
|
||||
|
||||
var reporter = new ProgressReporter();
|
||||
reporter.addListener(function (rep) {
|
||||
var progress = rep.getProgress();
|
||||
|
||||
if (label.textContent !== string) {
|
||||
label.textContent = string;
|
||||
}
|
||||
|
||||
if (isNaN(progress)) {
|
||||
bar.removeAttribute("value");
|
||||
} else {
|
||||
bar.value = progress;
|
||||
}
|
||||
});
|
||||
|
||||
gMainArea.appendChild(pane);
|
||||
Parser.updateLogSetting();
|
||||
|
||||
return reporter;
|
||||
}
|
@ -1,271 +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";
|
||||
|
||||
Array.prototype.clone = function() { return this.slice(0); }
|
||||
|
||||
function makeSample(frames, extraInfo, lines) {
|
||||
return {
|
||||
frames: frames,
|
||||
extraInfo: extraInfo,
|
||||
lines: lines
|
||||
};
|
||||
}
|
||||
|
||||
function cloneSample(sample) {
|
||||
return makeSample(sample.frames.clone(), sample.extraInfo, sample.lines.clone());
|
||||
}
|
||||
|
||||
function bucketsBySplittingArray(array, maxItemsPerBucket) {
|
||||
var buckets = [];
|
||||
while (buckets.length * maxItemsPerBucket < array.length) {
|
||||
buckets.push(array.slice(buckets.length * maxItemsPerBucket,
|
||||
(buckets.length + 1) * maxItemsPerBucket));
|
||||
}
|
||||
return buckets;
|
||||
}
|
||||
|
||||
var gParserWorker = new Worker("profiler/cleopatra/js/parserWorker.js");
|
||||
gParserWorker.nextRequestID = 0;
|
||||
|
||||
function WorkerRequest(worker) {
|
||||
var self = this;
|
||||
this._eventListeners = {};
|
||||
var requestID = worker.nextRequestID++;
|
||||
this._requestID = requestID;
|
||||
this._worker = worker;
|
||||
this._totalReporter = new ProgressReporter();
|
||||
this._totalReporter.addListener(function (reporter) {
|
||||
self._fireEvent("progress", reporter.getProgress(), reporter.getAction());
|
||||
})
|
||||
this._sendChunkReporter = this._totalReporter.addSubreporter(500);
|
||||
this._executeReporter = this._totalReporter.addSubreporter(3000);
|
||||
this._receiveChunkReporter = this._totalReporter.addSubreporter(100);
|
||||
this._totalReporter.begin("Processing task in worker...");
|
||||
var partialResult = null;
|
||||
function onMessageFromWorker(msg) {
|
||||
pendingMessages.push(msg);
|
||||
scheduleMessageProcessing();
|
||||
}
|
||||
function processMessage(msg) {
|
||||
var startTime = Date.now();
|
||||
var data = msg.data;
|
||||
var readTime = Date.now() - startTime;
|
||||
|
||||
if (data.requestID == requestID || !data.requestID) {
|
||||
switch(data.type) {
|
||||
case "error":
|
||||
self._sendChunkReporter.setAction("Error in worker: " + data.error);
|
||||
self._executeReporter.setAction("Error in worker: " + data.error);
|
||||
self._receiveChunkReporter.setAction("Error in worker: " + data.error);
|
||||
self._totalReporter.setAction("Error in worker: " + data.error);
|
||||
PROFILERERROR("Error in worker: " + data.error);
|
||||
self._fireEvent("error", data.error);
|
||||
break;
|
||||
case "progress":
|
||||
self._executeReporter.setProgress(data.progress);
|
||||
break;
|
||||
case "finished":
|
||||
self._executeReporter.finish();
|
||||
self._receiveChunkReporter.begin("Receiving data from worker...");
|
||||
self._receiveChunkReporter.finish();
|
||||
self._fireEvent("finished", data.result);
|
||||
worker.removeEventListener("message", onMessageFromWorker);
|
||||
break;
|
||||
case "finishedStart":
|
||||
partialResult = null;
|
||||
self._totalReceiveChunks = data.numChunks;
|
||||
self._gotReceiveChunks = 0;
|
||||
self._executeReporter.finish();
|
||||
self._receiveChunkReporter.begin("Receiving data from worker...");
|
||||
break;
|
||||
case "finishedChunk":
|
||||
partialResult = partialResult ? partialResult.concat(data.chunk) : data.chunk;
|
||||
var chunkIndex = self._gotReceiveChunks++;
|
||||
self._receiveChunkReporter.setProgress((chunkIndex + 1) / self._totalReceiveChunks);
|
||||
break;
|
||||
case "finishedEnd":
|
||||
self._receiveChunkReporter.finish();
|
||||
self._fireEvent("finished", partialResult);
|
||||
worker.removeEventListener("message", onMessageFromWorker);
|
||||
break;
|
||||
}
|
||||
// dump log if present
|
||||
if (data.log) {
|
||||
for (var line in data.log) {
|
||||
PROFILERLOG(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var pendingMessages = [];
|
||||
var messageProcessingTimer = 0;
|
||||
function processMessages() {
|
||||
messageProcessingTimer = 0;
|
||||
processMessage(pendingMessages.shift());
|
||||
if (pendingMessages.length)
|
||||
scheduleMessageProcessing();
|
||||
}
|
||||
function scheduleMessageProcessing() {
|
||||
if (messageProcessingTimer)
|
||||
return;
|
||||
messageProcessingTimer = setTimeout(processMessages, 10);
|
||||
}
|
||||
worker.addEventListener("message", onMessageFromWorker);
|
||||
}
|
||||
|
||||
WorkerRequest.prototype = {
|
||||
send: function WorkerRequest_send(task, taskData) {
|
||||
this._sendChunkReporter.begin("Sending data to worker...");
|
||||
var startTime = Date.now();
|
||||
this._worker.postMessage({
|
||||
requestID: this._requestID,
|
||||
task: task,
|
||||
taskData: taskData
|
||||
});
|
||||
var postTime = Date.now() - startTime;
|
||||
this._sendChunkReporter.finish();
|
||||
this._executeReporter.begin("Processing worker request...");
|
||||
},
|
||||
sendInChunks: function WorkerRequest_sendInChunks(task, taskData, params, maxChunkSize) {
|
||||
this._sendChunkReporter.begin("Sending data to worker...");
|
||||
var self = this;
|
||||
var chunks = bucketsBySplittingArray(taskData, maxChunkSize);
|
||||
var pendingMessages = [
|
||||
{
|
||||
requestID: this._requestID,
|
||||
task: "chunkedStart",
|
||||
numChunks: chunks.length
|
||||
}
|
||||
].concat(chunks.map(function (chunk) {
|
||||
return {
|
||||
requestID: self._requestID,
|
||||
task: "chunkedChunk",
|
||||
chunk: chunk
|
||||
};
|
||||
})).concat([
|
||||
{
|
||||
requestID: this._requestID,
|
||||
task: "chunkedEnd"
|
||||
},
|
||||
{
|
||||
requestID: this._requestID,
|
||||
params: params,
|
||||
task: task
|
||||
},
|
||||
]);
|
||||
var totalMessages = pendingMessages.length;
|
||||
var numSentMessages = 0;
|
||||
function postMessage(msg) {
|
||||
var msgIndex = numSentMessages++;
|
||||
var startTime = Date.now();
|
||||
self._worker.postMessage(msg);
|
||||
var postTime = Date.now() - startTime;
|
||||
self._sendChunkReporter.setProgress((msgIndex + 1) / totalMessages);
|
||||
}
|
||||
var messagePostingTimer = 0;
|
||||
function postMessages() {
|
||||
messagePostingTimer = 0;
|
||||
postMessage(pendingMessages.shift());
|
||||
if (pendingMessages.length) {
|
||||
scheduleMessagePosting();
|
||||
} else {
|
||||
self._sendChunkReporter.finish();
|
||||
self._executeReporter.begin("Processing worker request...");
|
||||
}
|
||||
}
|
||||
function scheduleMessagePosting() {
|
||||
if (messagePostingTimer)
|
||||
return;
|
||||
messagePostingTimer = setTimeout(postMessages, 10);
|
||||
}
|
||||
scheduleMessagePosting();
|
||||
},
|
||||
|
||||
// TODO: share code with TreeView
|
||||
addEventListener: function WorkerRequest_addEventListener(eventName, callbackFunction) {
|
||||
if (!(eventName in this._eventListeners))
|
||||
this._eventListeners[eventName] = [];
|
||||
if (this._eventListeners[eventName].indexOf(callbackFunction) != -1)
|
||||
return;
|
||||
this._eventListeners[eventName].push(callbackFunction);
|
||||
},
|
||||
removeEventListener: function WorkerRequest_removeEventListener(eventName, callbackFunction) {
|
||||
if (!(eventName in this._eventListeners))
|
||||
return;
|
||||
var index = this._eventListeners[eventName].indexOf(callbackFunction);
|
||||
if (index == -1)
|
||||
return;
|
||||
this._eventListeners[eventName].splice(index, 1);
|
||||
},
|
||||
_fireEvent: function WorkerRequest__fireEvent(eventName, eventObject, p1) {
|
||||
if (!(eventName in this._eventListeners))
|
||||
return;
|
||||
this._eventListeners[eventName].forEach(function (callbackFunction) {
|
||||
callbackFunction(eventObject, p1);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
var Parser = {
|
||||
parse: function Parser_parse(data, params) {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.sendInChunks("parseRawProfile", data, params, 3000000);
|
||||
return request;
|
||||
},
|
||||
|
||||
updateFilters: function Parser_updateFilters(filters) {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.send("updateFilters", {
|
||||
filters: filters,
|
||||
profileID: 0
|
||||
});
|
||||
return request;
|
||||
},
|
||||
|
||||
updateViewOptions: function Parser_updateViewOptions(options) {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.send("updateViewOptions", {
|
||||
options: options,
|
||||
profileID: 0
|
||||
});
|
||||
return request;
|
||||
},
|
||||
|
||||
getSerializedProfile: function Parser_getSerializedProfile(complete, callback) {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.send("getSerializedProfile", {
|
||||
profileID: 0,
|
||||
complete: complete
|
||||
});
|
||||
request.addEventListener("finished", callback);
|
||||
},
|
||||
|
||||
calculateHistogramData: function Parser_calculateHistogramData() {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.send("calculateHistogramData", {
|
||||
profileID: 0
|
||||
});
|
||||
return request;
|
||||
},
|
||||
|
||||
calculateDiagnosticItems: function Parser_calculateDiagnosticItems(meta) {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.send("calculateDiagnosticItems", {
|
||||
profileID: 0,
|
||||
meta: meta
|
||||
});
|
||||
return request;
|
||||
},
|
||||
|
||||
updateLogSetting: function Parser_updateLogSetting() {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.send("initWorker", {
|
||||
debugLog: gDebugLog,
|
||||
debugTrace: gDebugTrace,
|
||||
});
|
||||
return request;
|
||||
},
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,29 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
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.
|
||||
*/
|
||||
var gStrings = {
|
||||
// This strings are here so that Cleopatra code could use a simple object
|
||||
// lookup. This makes it easier to merge upstream changes.
|
||||
"Complete Profile": L10N.getStr("profiler.completeProfile"),
|
||||
"Sample Range": L10N.getStr("profiler.sampleRange"),
|
||||
"Running Time": L10N.getStr("profiler.runningTime"),
|
||||
"Self": L10N.getStr("profiler.self"),
|
||||
"Symbol Name": L10N.getStr("profiler.symbolName"),
|
||||
|
||||
getStr: function (name) {
|
||||
return L10N.getStr(name);
|
||||
},
|
||||
|
||||
getFormatStr: function (name, params) {
|
||||
return L10N.getFormatStr(name, params);
|
||||
}
|
||||
};
|
@ -1,702 +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";
|
||||
|
||||
var kMaxChunkDuration = 30; // ms
|
||||
|
||||
function escapeHTML(html) {
|
||||
var pre = document.createElementNS("http://www.w3.org/1999/xhtml", "pre");
|
||||
var text = document.createTextNode(html);
|
||||
pre.appendChild(text);
|
||||
return pre.innerHTML;
|
||||
}
|
||||
|
||||
RegExp.escape = function(text) {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
}
|
||||
|
||||
var requestAnimationFrame = window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function(callback, element) {
|
||||
return window.setTimeout(callback, 1000 / 60);
|
||||
};
|
||||
|
||||
var cancelAnimationFrame = window.webkitCancelAnimationFrame ||
|
||||
window.mozCancelAnimationFrame ||
|
||||
window.oCancelAnimationFrame ||
|
||||
window.msCancelAnimationFrame ||
|
||||
function(req) {
|
||||
window.clearTimeout(req);
|
||||
};
|
||||
|
||||
function TreeView() {
|
||||
this._eventListeners = {};
|
||||
this._pendingActions = [];
|
||||
this._pendingActionsProcessingCallback = null;
|
||||
|
||||
this._container = document.createElement("div");
|
||||
this._container.className = "treeViewContainer";
|
||||
this._container.setAttribute("tabindex", "0"); // make it focusable
|
||||
|
||||
this._header = document.createElement("ul");
|
||||
this._header.className = "treeHeader";
|
||||
this._container.appendChild(this._header);
|
||||
|
||||
this._verticalScrollbox = document.createElement("div");
|
||||
this._verticalScrollbox.className = "treeViewVerticalScrollbox";
|
||||
this._container.appendChild(this._verticalScrollbox);
|
||||
|
||||
this._leftColumnBackground = document.createElement("div");
|
||||
this._leftColumnBackground.className = "leftColumnBackground";
|
||||
this._verticalScrollbox.appendChild(this._leftColumnBackground);
|
||||
|
||||
this._horizontalScrollbox = document.createElement("div");
|
||||
this._horizontalScrollbox.className = "treeViewHorizontalScrollbox";
|
||||
this._verticalScrollbox.appendChild(this._horizontalScrollbox);
|
||||
|
||||
this._styleElement = document.createElement("style");
|
||||
this._styleElement.setAttribute("type", "text/css");
|
||||
this._container.appendChild(this._styleElement);
|
||||
|
||||
this._contextMenu = document.createElement("menu");
|
||||
this._contextMenu.setAttribute("type", "context");
|
||||
this._contextMenu.id = "contextMenuForTreeView" + TreeView.instanceCounter++;
|
||||
this._container.appendChild(this._contextMenu);
|
||||
|
||||
this._busyCover = document.createElement("div");
|
||||
this._busyCover.className = "busyCover";
|
||||
this._container.appendChild(this._busyCover);
|
||||
this._abortToggleAll = false;
|
||||
this.initSelection = true;
|
||||
|
||||
var self = this;
|
||||
this._container.onkeydown = function (e) {
|
||||
self._onkeypress(e);
|
||||
};
|
||||
this._container.onkeypress = function (e) {
|
||||
// on key down gives us '8' and mapping shift+8='*' may not be portable.
|
||||
if (String.fromCharCode(e.charCode) == '*')
|
||||
self._onkeypress(e);
|
||||
};
|
||||
this._container.onclick = function (e) {
|
||||
self._onclick(e);
|
||||
};
|
||||
this._verticalScrollbox.addEventListener("contextmenu", function(event) {
|
||||
self._populateContextMenu(event);
|
||||
}, true);
|
||||
this._setUpScrolling();
|
||||
};
|
||||
TreeView.instanceCounter = 0;
|
||||
|
||||
TreeView.prototype = {
|
||||
getContainer: function TreeView_getContainer() {
|
||||
return this._container;
|
||||
},
|
||||
setColumns: function TreeView_setColumns(columns) {
|
||||
this._header.innerHTML = "";
|
||||
for (var i = 0; i < columns.length; i++) {
|
||||
var li = document.createElement("li");
|
||||
li.className = "treeColumnHeader treeColumnHeader" + i;
|
||||
li.id = columns[i].name + "Header";
|
||||
li.textContent = columns[i].title;
|
||||
this._header.appendChild(li);
|
||||
}
|
||||
},
|
||||
dataIsOutdated: function TreeView_dataIsOutdated() {
|
||||
this._busyCover.classList.add("busy");
|
||||
},
|
||||
display: function TreeView_display(data, resources, filterByName) {
|
||||
this._busyCover.classList.remove("busy");
|
||||
this._filterByName = filterByName;
|
||||
this._resources = resources;
|
||||
this._addResourceIconStyles();
|
||||
this._filterByNameReg = null; // lazy init
|
||||
if (this._filterByName === "")
|
||||
this._filterByName = null;
|
||||
this._horizontalScrollbox.innerHTML = "";
|
||||
this._horizontalScrollbox.data = data[0].getData();
|
||||
if (this._pendingActionsProcessingCallback) {
|
||||
cancelAnimationFrame(this._pendingActionsProcessingCallback);
|
||||
this._pendingActionsProcessingCallback = 0;
|
||||
}
|
||||
this._pendingActions = [];
|
||||
|
||||
this._pendingActions.push({
|
||||
parentElement: this._horizontalScrollbox,
|
||||
parentNode: null,
|
||||
data: data[0].getData()
|
||||
});
|
||||
this._processPendingActionsChunk();
|
||||
changeFocus(this._container);
|
||||
},
|
||||
// Provide a snapshot of the reverse selection to restore with 'invert callback'
|
||||
getReverseSelectionSnapshot: function TreeView__getReverseSelectionSnapshot(isJavascriptOnly) {
|
||||
var snapshot = [];
|
||||
|
||||
if (!this._selectedNode) {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
var curr = this._selectedNode.data;
|
||||
|
||||
while(curr) {
|
||||
if (isJavascriptOnly && curr.isJSFrame || !isJavascriptOnly) {
|
||||
snapshot.push(curr.name);
|
||||
//dump(JSON.stringify(curr.name) + "\n");
|
||||
}
|
||||
if (curr.treeChildren && curr.treeChildren.length >= 1) {
|
||||
curr = curr.treeChildren[0].getData();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot.reverse();
|
||||
},
|
||||
// Provide a snapshot of the current selection to restore
|
||||
getSelectionSnapshot: function TreeView__getSelectionSnapshot(isJavascriptOnly) {
|
||||
var snapshot = [];
|
||||
var curr = this._selectedNode;
|
||||
|
||||
while(curr) {
|
||||
if (isJavascriptOnly && curr.data.isJSFrame || !isJavascriptOnly) {
|
||||
snapshot.push(curr.data.name);
|
||||
//dump(JSON.stringify(curr.data.name) + "\n");
|
||||
}
|
||||
curr = curr.treeParent;
|
||||
}
|
||||
|
||||
return snapshot.reverse();
|
||||
},
|
||||
setSelection: function TreeView_setSelection(frames) {
|
||||
this.restoreSelectionSnapshot(frames, false);
|
||||
},
|
||||
// Take a selection snapshot and restore the selection
|
||||
restoreSelectionSnapshot: function TreeView_restoreSelectionSnapshot(snapshot, allowNonContiguous) {
|
||||
var currNode = this._horizontalScrollbox.firstChild;
|
||||
if (currNode.data.name == snapshot[0] || snapshot[0] == "(total)") {
|
||||
snapshot.shift();
|
||||
}
|
||||
//dump("len: " + snapshot.length + "\n");
|
||||
next_level: while (currNode && snapshot.length > 0) {
|
||||
this._toggle(currNode, false, true);
|
||||
this._syncProcessPendingActionProcessing();
|
||||
for (var i = 0; i < currNode.treeChildren.length; i++) {
|
||||
if (currNode.treeChildren[i].data.name == snapshot[0]) {
|
||||
snapshot.shift();
|
||||
this._toggle(currNode, false, true);
|
||||
currNode = currNode.treeChildren[i];
|
||||
continue next_level;
|
||||
}
|
||||
}
|
||||
if (allowNonContiguous) {
|
||||
// We need to do a Breadth-first search to find a match
|
||||
var pendingSearch = [currNode.data];
|
||||
while (pendingSearch.length > 0) {
|
||||
var node = pendingSearch.shift();
|
||||
if (!node.treeChildren)
|
||||
continue;
|
||||
for (var i = 0; i < node.treeChildren.length; i++) {
|
||||
var childNode = node.treeChildren[i].getData();
|
||||
if (childNode.name == snapshot[0]) {
|
||||
//dump("found: " + childNode.name + "\n");
|
||||
snapshot.shift();
|
||||
var nodesToToggle = [childNode];
|
||||
while (nodesToToggle[0].name != currNode.data.name) {
|
||||
nodesToToggle.splice(0, 0, nodesToToggle[0].parent);
|
||||
}
|
||||
var lastToggle = currNode;
|
||||
for (var j = 0; j < nodesToToggle.length; j++) {
|
||||
for (var k = 0; k < lastToggle.treeChildren.length; k++) {
|
||||
if (lastToggle.treeChildren[k].data.name == nodesToToggle[j].name) {
|
||||
//dump("Expend: " + nodesToToggle[j].name + "\n");
|
||||
this._toggle(lastToggle.treeChildren[k], false, true);
|
||||
lastToggle = lastToggle.treeChildren[k];
|
||||
this._syncProcessPendingActionProcessing();
|
||||
}
|
||||
}
|
||||
}
|
||||
currNode = lastToggle;
|
||||
continue next_level;
|
||||
}
|
||||
//dump("pending: " + childNode.name + "\n");
|
||||
pendingSearch.push(childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
break; // Didn't find child node matching
|
||||
}
|
||||
|
||||
if (currNode == this._horizontalScrollbox) {
|
||||
PROFILERERROR("Failed to restore selection, could not find root.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
this._toggle(currNode, true, true);
|
||||
this._select(currNode);
|
||||
},
|
||||
_processPendingActionsChunk: function TreeView__processPendingActionsChunk(isSync) {
|
||||
this._pendingActionsProcessingCallback = 0;
|
||||
|
||||
var startTime = Date.now();
|
||||
var endTime = startTime + kMaxChunkDuration;
|
||||
while ((isSync == true || Date.now() < endTime) && this._pendingActions.length > 0) {
|
||||
this._processOneAction(this._pendingActions.shift());
|
||||
}
|
||||
this._scrollHeightChanged();
|
||||
|
||||
this._schedulePendingActionProcessing();
|
||||
},
|
||||
_schedulePendingActionProcessing: function TreeView__schedulePendingActionProcessing() {
|
||||
if (!this._pendingActionsProcessingCallback && this._pendingActions.length > 0) {
|
||||
var self = this;
|
||||
this._pendingActionsProcessingCallback = requestAnimationFrame(function () {
|
||||
self._processPendingActionsChunk();
|
||||
});
|
||||
}
|
||||
},
|
||||
_syncProcessPendingActionProcessing: function TreeView__syncProcessPendingActionProcessing() {
|
||||
this._processPendingActionsChunk(true);
|
||||
},
|
||||
_processOneAction: function TreeView__processOneAction(action) {
|
||||
var li = this._createTree(action.parentElement, action.parentNode, action.data);
|
||||
if ("allChildrenCollapsedValue" in action) {
|
||||
if (this._abortToggleAll)
|
||||
return;
|
||||
this._toggleAll(li, action.allChildrenCollapsedValue, true);
|
||||
}
|
||||
},
|
||||
addEventListener: function TreeView_addEventListener(eventName, callbackFunction) {
|
||||
if (!(eventName in this._eventListeners))
|
||||
this._eventListeners[eventName] = [];
|
||||
if (this._eventListeners[eventName].indexOf(callbackFunction) != -1)
|
||||
return;
|
||||
this._eventListeners[eventName].push(callbackFunction);
|
||||
},
|
||||
removeEventListener: function TreeView_removeEventListener(eventName, callbackFunction) {
|
||||
if (!(eventName in this._eventListeners))
|
||||
return;
|
||||
var index = this._eventListeners[eventName].indexOf(callbackFunction);
|
||||
if (index == -1)
|
||||
return;
|
||||
this._eventListeners[eventName].splice(index, 1);
|
||||
},
|
||||
_fireEvent: function TreeView__fireEvent(eventName, eventObject) {
|
||||
if (!(eventName in this._eventListeners))
|
||||
return;
|
||||
this._eventListeners[eventName].forEach(function (callbackFunction) {
|
||||
callbackFunction(eventObject);
|
||||
});
|
||||
},
|
||||
_setUpScrolling: function TreeView__setUpScrolling() {
|
||||
var waitingForPaint = false;
|
||||
var accumulatedDeltaX = 0;
|
||||
var accumulatedDeltaY = 0;
|
||||
var self = this;
|
||||
function scrollListener(e) {
|
||||
if (!waitingForPaint) {
|
||||
requestAnimationFrame(function () {
|
||||
self._horizontalScrollbox.scrollLeft += accumulatedDeltaX;
|
||||
self._verticalScrollbox.scrollTop += accumulatedDeltaY;
|
||||
accumulatedDeltaX = 0;
|
||||
accumulatedDeltaY = 0;
|
||||
waitingForPaint = false;
|
||||
});
|
||||
waitingForPaint = true;
|
||||
}
|
||||
if (e.axis == e.HORIZONTAL_AXIS) {
|
||||
accumulatedDeltaX += e.detail;
|
||||
} else {
|
||||
accumulatedDeltaY += e.detail;
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
this._verticalScrollbox.addEventListener("MozMousePixelScroll", scrollListener, false);
|
||||
this._verticalScrollbox.cleanUp = function () {
|
||||
self._verticalScrollbox.removeEventListener("MozMousePixelScroll", scrollListener, false);
|
||||
};
|
||||
},
|
||||
_scrollHeightChanged: function TreeView__scrollHeightChanged() {
|
||||
if (!this._pendingScrollHeightChanged) {
|
||||
var self = this;
|
||||
this._pendingScrollHeightChanged = setTimeout(function() {
|
||||
self._leftColumnBackground.style.height = self._horizontalScrollbox.getBoundingClientRect().height + 'px';
|
||||
self._pendingScrollHeightChanged = null;
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
_createTree: function TreeView__createTree(parentElement, parentNode, data) {
|
||||
var div = document.createElement("div");
|
||||
div.className = "treeViewNode collapsed";
|
||||
var hasChildren = ("children" in data) && (data.children.length > 0);
|
||||
if (!hasChildren)
|
||||
div.classList.add("leaf");
|
||||
var treeLine = document.createElement("div");
|
||||
treeLine.className = "treeLine";
|
||||
treeLine.innerHTML = this._HTMLForFunction(data);
|
||||
div.depth = parentNode ? parentNode.depth + 1 : 0;
|
||||
div.style.marginLeft = div.depth + "em";
|
||||
// When this item is toggled we will expand its children
|
||||
div.pendingExpand = [];
|
||||
div.treeLine = treeLine;
|
||||
div.data = data;
|
||||
// Useful for debugging
|
||||
//this.uniqueID = this.uniqueID || 0;
|
||||
//div.id = "Node" + this.uniqueID++;
|
||||
div.appendChild(treeLine);
|
||||
div.treeChildren = [];
|
||||
div.treeParent = parentNode;
|
||||
if (hasChildren) {
|
||||
for (var i = 0; i < data.children.length; ++i) {
|
||||
div.pendingExpand.push({parentElement: this._horizontalScrollbox, parentNode: div, data: data.children[i].getData() });
|
||||
}
|
||||
}
|
||||
if (parentNode) {
|
||||
parentNode.treeChildren.push(div);
|
||||
}
|
||||
if (parentNode != null) {
|
||||
var nextTo;
|
||||
if (parentNode.treeChildren.length > 1) {
|
||||
nextTo = parentNode.treeChildren[parentNode.treeChildren.length-2].nextSibling;
|
||||
} else {
|
||||
nextTo = parentNode.nextSibling;
|
||||
}
|
||||
parentElement.insertBefore(div, nextTo);
|
||||
} else {
|
||||
parentElement.appendChild(div);
|
||||
}
|
||||
return div;
|
||||
},
|
||||
_addResourceIconStyles: function TreeView__addResourceIconStyles() {
|
||||
var styles = [];
|
||||
for (var resourceName in this._resources) {
|
||||
var resource = this._resources[resourceName];
|
||||
if (resource.icon) {
|
||||
styles.push('.resourceIcon[data-resource="' + resourceName + '"] { background-image: url("' + resource.icon + '"); }');
|
||||
}
|
||||
}
|
||||
this._styleElement.textContent = styles.join("\n");
|
||||
},
|
||||
_populateContextMenu: function TreeView__populateContextMenu(event) {
|
||||
this._verticalScrollbox.setAttribute("contextmenu", "");
|
||||
|
||||
var target = event.target;
|
||||
if (target.classList.contains("expandCollapseButton") ||
|
||||
target.classList.contains("focusCallstackButton"))
|
||||
return;
|
||||
|
||||
var li = this._getParentTreeViewNode(target);
|
||||
if (!li)
|
||||
return;
|
||||
|
||||
this._select(li);
|
||||
|
||||
this._contextMenu.innerHTML = "";
|
||||
|
||||
var self = this;
|
||||
this._contextMenuForFunction(li.data).forEach(function (menuItem) {
|
||||
var menuItemNode = document.createElement("menuitem");
|
||||
menuItemNode.onclick = (function (menuItem) {
|
||||
return function() {
|
||||
self._contextMenuClick(li.data, menuItem);
|
||||
};
|
||||
})(menuItem);
|
||||
menuItemNode.label = menuItem;
|
||||
self._contextMenu.appendChild(menuItemNode);
|
||||
});
|
||||
|
||||
this._verticalScrollbox.setAttribute("contextmenu", this._contextMenu.id);
|
||||
},
|
||||
_contextMenuClick: function TreeView__contextMenuClick(node, menuItem) {
|
||||
this._fireEvent("contextMenuClick", { node: node, menuItem: menuItem });
|
||||
},
|
||||
_contextMenuForFunction: function TreeView__contextMenuForFunction(node) {
|
||||
// TODO move me outside tree.js
|
||||
var menu = [];
|
||||
if (node.library && (
|
||||
node.library.toLowerCase() == "lib_xul" ||
|
||||
node.library.toLowerCase() == "lib_xul.dll"
|
||||
)) {
|
||||
menu.push("View Source");
|
||||
}
|
||||
if (node.isJSFrame && node.scriptLocation) {
|
||||
menu.push("View JS Source");
|
||||
}
|
||||
menu.push("Focus Frame");
|
||||
menu.push("Focus Callstack");
|
||||
menu.push("Google Search");
|
||||
menu.push("Plugin View: Pie");
|
||||
menu.push("Plugin View: Tree");
|
||||
return menu;
|
||||
},
|
||||
_HTMLForFunction: function TreeView__HTMLForFunction(node) {
|
||||
var nodeName = escapeHTML(node.name);
|
||||
var resource = this._resources[node.library] || {};
|
||||
var libName = escapeHTML(resource.name || "");
|
||||
if (this._filterByName) {
|
||||
if (!this._filterByNameReg) {
|
||||
this._filterByName = RegExp.escape(this._filterByName);
|
||||
this._filterByNameReg = new RegExp("(" + this._filterByName + ")","gi");
|
||||
}
|
||||
nodeName = nodeName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>");
|
||||
libName = libName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>");
|
||||
}
|
||||
var samplePercentage;
|
||||
if (isNaN(node.ratio)) {
|
||||
samplePercentage = "";
|
||||
} else {
|
||||
samplePercentage = (100 * node.ratio).toFixed(1) + "%";
|
||||
}
|
||||
return '<input type="button" value="Expand / Collapse" class="expandCollapseButton" tabindex="-1"> ' +
|
||||
'<span class="sampleCount">' + node.counter + '</span> ' +
|
||||
'<span class="samplePercentage">' + samplePercentage + '</span> ' +
|
||||
'<span class="selfSampleCount">' + node.selfCounter + '</span> ' +
|
||||
'<span class="resourceIcon" data-resource="' + node.library + '"></span> ' +
|
||||
'<span class="functionName">' + nodeName + '</span>' +
|
||||
'<span class="libraryName">' + libName + '</span>' +
|
||||
((nodeName === '(total)' || gHideSourceLinks) ? '' :
|
||||
'<input type="button" value="Focus Callstack" title="Focus Callstack" class="focusCallstackButton" tabindex="-1">');
|
||||
},
|
||||
_resolveChildren: function TreeView__resolveChildren(div, childrenCollapsedValue) {
|
||||
while (div.pendingExpand != null && div.pendingExpand.length > 0) {
|
||||
var pendingExpand = div.pendingExpand.shift();
|
||||
pendingExpand.allChildrenCollapsedValue = childrenCollapsedValue;
|
||||
this._pendingActions.push(pendingExpand);
|
||||
this._schedulePendingActionProcessing();
|
||||
}
|
||||
},
|
||||
_showChild: function TreeView__showChild(div, isVisible) {
|
||||
for (var i = 0; i < div.treeChildren.length; i++) {
|
||||
div.treeChildren[i].style.display = isVisible?"":"none";
|
||||
if (!isVisible) {
|
||||
div.treeChildren[i].classList.add("collapsed");
|
||||
this._showChild(div.treeChildren[i], isVisible);
|
||||
}
|
||||
}
|
||||
},
|
||||
_toggle: function TreeView__toggle(div, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
|
||||
var currentCollapsedValue = this._isCollapsed(div);
|
||||
if (newCollapsedValue === undefined)
|
||||
newCollapsedValue = !currentCollapsedValue;
|
||||
if (newCollapsedValue) {
|
||||
div.classList.add("collapsed");
|
||||
this._showChild(div, false);
|
||||
} else {
|
||||
this._resolveChildren(div, true);
|
||||
div.classList.remove("collapsed");
|
||||
this._showChild(div, true);
|
||||
}
|
||||
if (!suppressScrollHeightNotification)
|
||||
this._scrollHeightChanged();
|
||||
},
|
||||
_toggleAll: function TreeView__toggleAll(subtreeRoot, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
|
||||
|
||||
// Reset abort
|
||||
this._abortToggleAll = false;
|
||||
|
||||
// Expands / collapses all child nodes, too.
|
||||
|
||||
if (newCollapsedValue === undefined)
|
||||
newCollapsedValue = !this._isCollapsed(subtreeRoot);
|
||||
if (!newCollapsedValue) {
|
||||
// expanding
|
||||
this._resolveChildren(subtreeRoot, newCollapsedValue);
|
||||
}
|
||||
this._toggle(subtreeRoot, newCollapsedValue, true);
|
||||
for (var i = 0; i < subtreeRoot.treeChildren.length; ++i) {
|
||||
this._toggleAll(subtreeRoot.treeChildren[i], newCollapsedValue, true);
|
||||
}
|
||||
if (!suppressScrollHeightNotification)
|
||||
this._scrollHeightChanged();
|
||||
},
|
||||
_getParent: function TreeView__getParent(div) {
|
||||
return div.treeParent;
|
||||
},
|
||||
_getFirstChild: function TreeView__getFirstChild(div) {
|
||||
if (this._isCollapsed(div))
|
||||
return null;
|
||||
var child = div.treeChildren[0];
|
||||
return child;
|
||||
},
|
||||
_getLastChild: function TreeView__getLastChild(div) {
|
||||
if (this._isCollapsed(div))
|
||||
return div;
|
||||
var lastChild = div.treeChildren[div.treeChildren.length-1];
|
||||
if (lastChild == null)
|
||||
return div;
|
||||
return this._getLastChild(lastChild);
|
||||
},
|
||||
_getPrevSib: function TreeView__getPevSib(div) {
|
||||
if (div.treeParent == null)
|
||||
return null;
|
||||
var nodeIndex = div.treeParent.treeChildren.indexOf(div);
|
||||
if (nodeIndex == 0)
|
||||
return null;
|
||||
return div.treeParent.treeChildren[nodeIndex-1];
|
||||
},
|
||||
_getNextSib: function TreeView__getNextSib(div) {
|
||||
if (div.treeParent == null)
|
||||
return null;
|
||||
var nodeIndex = div.treeParent.treeChildren.indexOf(div);
|
||||
if (nodeIndex == div.treeParent.treeChildren.length - 1)
|
||||
return this._getNextSib(div.treeParent);
|
||||
return div.treeParent.treeChildren[nodeIndex+1];
|
||||
},
|
||||
_scheduleScrollIntoView: function TreeView__scheduleScrollIntoView(element, maxImportantWidth) {
|
||||
// Schedule this on the animation frame otherwise we may run this more then once per frames
|
||||
// causing more work then needed.
|
||||
var self = this;
|
||||
if (self._pendingAnimationFrame != null) {
|
||||
return;
|
||||
}
|
||||
self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() {
|
||||
cancelAnimationFrame(self._pendingAnimationFrame);
|
||||
self._pendingAnimationFrame = null;
|
||||
self._scrollIntoView(element, maxImportantWidth);
|
||||
});
|
||||
},
|
||||
_scrollIntoView: function TreeView__scrollIntoView(element, maxImportantWidth) {
|
||||
// Make sure that element is inside the visible part of our scrollbox by
|
||||
// adjusting the scroll positions. If element is wider or
|
||||
// higher than the scroll port, the left and top edges are prioritized over
|
||||
// the right and bottom edges.
|
||||
// If maxImportantWidth is set, parts of the beyond this widths are
|
||||
// considered as not important; they'll not be moved into view.
|
||||
|
||||
if (maxImportantWidth === undefined)
|
||||
maxImportantWidth = Infinity;
|
||||
|
||||
var visibleRect = {
|
||||
left: this._horizontalScrollbox.getBoundingClientRect().left + 150, // TODO: un-hardcode 150
|
||||
top: this._verticalScrollbox.getBoundingClientRect().top,
|
||||
right: this._horizontalScrollbox.getBoundingClientRect().right,
|
||||
bottom: this._verticalScrollbox.getBoundingClientRect().bottom
|
||||
}
|
||||
var r = element.getBoundingClientRect();
|
||||
var right = Math.min(r.right, r.left + maxImportantWidth);
|
||||
var leftCutoff = visibleRect.left - r.left;
|
||||
var rightCutoff = right - visibleRect.right;
|
||||
var topCutoff = visibleRect.top - r.top;
|
||||
var bottomCutoff = r.bottom - visibleRect.bottom;
|
||||
if (leftCutoff > 0)
|
||||
this._horizontalScrollbox.scrollLeft -= leftCutoff;
|
||||
else if (rightCutoff > 0)
|
||||
this._horizontalScrollbox.scrollLeft += Math.min(rightCutoff, -leftCutoff);
|
||||
if (topCutoff > 0)
|
||||
this._verticalScrollbox.scrollTop -= topCutoff;
|
||||
else if (bottomCutoff > 0)
|
||||
this._verticalScrollbox.scrollTop += Math.min(bottomCutoff, -topCutoff);
|
||||
},
|
||||
_select: function TreeView__select(li) {
|
||||
if (this._selectedNode != null) {
|
||||
this._selectedNode.treeLine.classList.remove("selected");
|
||||
this._selectedNode = null;
|
||||
}
|
||||
if (li) {
|
||||
li.treeLine.classList.add("selected");
|
||||
this._selectedNode = li;
|
||||
var functionName = li.treeLine.querySelector(".functionName");
|
||||
this._scheduleScrollIntoView(functionName, 400);
|
||||
this._fireEvent("select", li.data);
|
||||
}
|
||||
updateDocumentURL();
|
||||
},
|
||||
_isCollapsed: function TreeView__isCollapsed(div) {
|
||||
return div.classList.contains("collapsed");
|
||||
},
|
||||
_getParentTreeViewNode: function TreeView__getParentTreeViewNode(node) {
|
||||
while (node) {
|
||||
if (node.nodeType != node.ELEMENT_NODE)
|
||||
break;
|
||||
if (node.classList.contains("treeViewNode"))
|
||||
return node;
|
||||
node = node.parentNode;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
_onclick: function TreeView__onclick(event) {
|
||||
var target = event.target;
|
||||
var node = this._getParentTreeViewNode(target);
|
||||
if (!node)
|
||||
return;
|
||||
if (target.classList.contains("expandCollapseButton")) {
|
||||
if (event.altKey)
|
||||
this._toggleAll(node);
|
||||
else
|
||||
this._toggle(node);
|
||||
} else if (target.classList.contains("focusCallstackButton")) {
|
||||
this._fireEvent("focusCallstackButtonClicked", node.data);
|
||||
} else {
|
||||
this._select(node);
|
||||
if (event.detail == 2) // dblclick
|
||||
this._toggle(node);
|
||||
}
|
||||
},
|
||||
_onkeypress: function TreeView__onkeypress(event) {
|
||||
if (event.ctrlKey || event.altKey || event.metaKey)
|
||||
return;
|
||||
|
||||
this._abortToggleAll = true;
|
||||
|
||||
var selected = this._selectedNode;
|
||||
if (event.keyCode < 37 || event.keyCode > 40) {
|
||||
if (event.keyCode != 0 ||
|
||||
String.fromCharCode(event.charCode) != '*') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if (!selected)
|
||||
return;
|
||||
if (event.keyCode == 37) { // KEY_LEFT
|
||||
var isCollapsed = this._isCollapsed(selected);
|
||||
if (!isCollapsed) {
|
||||
this._toggle(selected);
|
||||
} else {
|
||||
var parent = this._getParent(selected);
|
||||
if (parent != null) {
|
||||
this._select(parent);
|
||||
}
|
||||
}
|
||||
} else if (event.keyCode == 38) { // KEY_UP
|
||||
var prevSib = this._getPrevSib(selected);
|
||||
var parent = this._getParent(selected);
|
||||
if (prevSib != null) {
|
||||
this._select(this._getLastChild(prevSib));
|
||||
} else if (parent != null) {
|
||||
this._select(parent);
|
||||
}
|
||||
} else if (event.keyCode == 39) { // KEY_RIGHT
|
||||
var isCollapsed = this._isCollapsed(selected);
|
||||
if (isCollapsed) {
|
||||
this._toggle(selected);
|
||||
this._syncProcessPendingActionProcessing();
|
||||
} else {
|
||||
// Do KEY_DOWN
|
||||
var nextSib = this._getNextSib(selected);
|
||||
var child = this._getFirstChild(selected);
|
||||
if (child != null) {
|
||||
this._select(child);
|
||||
} else if (nextSib) {
|
||||
this._select(nextSib);
|
||||
}
|
||||
}
|
||||
} else if (event.keyCode == 40) { // KEY_DOWN
|
||||
var nextSib = this._getNextSib(selected);
|
||||
var child = this._getFirstChild(selected);
|
||||
if (child != null) {
|
||||
this._select(child);
|
||||
} else if (nextSib) {
|
||||
this._select(nextSib);
|
||||
}
|
||||
} else if (String.fromCharCode(event.charCode) == '*') {
|
||||
this._toggleAll(selected);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,137 +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 gcli = require('gcli/index');
|
||||
|
||||
loader.lazyGetter(this, "gDevTools",
|
||||
() => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools);
|
||||
|
||||
|
||||
module.exports.items = [
|
||||
{
|
||||
name: "profiler",
|
||||
description: gcli.lookup("profilerDesc"),
|
||||
manual: gcli.lookup("profilerManual")
|
||||
},
|
||||
{
|
||||
name: "profiler open",
|
||||
description: gcli.lookup("profilerOpenDesc"),
|
||||
exec: function (args, context) {
|
||||
return gDevTools.showToolbox(context.environment.target, "jsprofiler")
|
||||
.then(function () null);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "profiler close",
|
||||
description: gcli.lookup("profilerCloseDesc"),
|
||||
exec: function (args, context) {
|
||||
let toolbox = gDevTools.getToolbox(context.environment.target);
|
||||
let panel = (toolbox == null) ? null : toolbox.getPanel(id);
|
||||
if (panel == null)
|
||||
return;
|
||||
|
||||
return gDevTools.closeToolbox(context.environment.target)
|
||||
.then(function () null);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "profiler start",
|
||||
description: gcli.lookup("profilerStartDesc"),
|
||||
returnType: "string",
|
||||
exec: function (args, context) {
|
||||
let target = context.environment.target
|
||||
return gDevTools.showToolbox(target, "jsprofiler").then(toolbox => {
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
|
||||
if (panel.recordingProfile)
|
||||
throw gcli.lookup("profilerAlreadyStarted2");
|
||||
|
||||
panel.toggleRecording();
|
||||
return gcli.lookup("profilerStarted2");
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "profiler stop",
|
||||
description: gcli.lookup("profilerStopDesc"),
|
||||
returnType: "string",
|
||||
exec: function (args, context) {
|
||||
let target = context.environment.target
|
||||
return gDevTools.showToolbox(target, "jsprofiler").then(toolbox => {
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
|
||||
if (!panel.recordingProfile)
|
||||
throw gcli.lookup("profilerNotStarted3");
|
||||
|
||||
panel.toggleRecording();
|
||||
return gcli.lookup("profilerStopped");
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "profiler list",
|
||||
description: gcli.lookup("profilerListDesc"),
|
||||
returnType: "profileList",
|
||||
exec: function (args, context) {
|
||||
let toolbox = gDevTools.getToolbox(context.environment.target);
|
||||
let panel = (toolbox == null) ? null : toolbox.getPanel("jsprofiler");
|
||||
|
||||
if (panel == null) {
|
||||
throw gcli.lookup("profilerNotReady");
|
||||
}
|
||||
|
||||
let profileList = [];
|
||||
for ([ uid, profile ] of panel.profiles) {
|
||||
profileList.push({ name: profile.name, started: profile.isStarted });
|
||||
}
|
||||
return profileList;
|
||||
}
|
||||
},
|
||||
{
|
||||
item: "converter",
|
||||
from: "profileList",
|
||||
to: "view",
|
||||
exec: function(profileList, context) {
|
||||
return {
|
||||
html: "<div>" +
|
||||
" <ol>" +
|
||||
" <li forEach='profile of ${profiles}'>${profile.name}</li>" +
|
||||
" ${profile.name} ${profile.started ? '*' : ''}" +
|
||||
" </li>" +
|
||||
" </ol>" +
|
||||
"</div>",
|
||||
data: { profiles: profileList.profiles },
|
||||
options: { allowEval: true }
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "profiler show",
|
||||
description: gcli.lookup("profilerShowDesc"),
|
||||
params: [
|
||||
{
|
||||
name: "name",
|
||||
type: "string",
|
||||
manual: gcli.lookup("profilerShowManual")
|
||||
}
|
||||
],
|
||||
|
||||
exec: function (args, context) {
|
||||
let toolbox = gDevTools.getToolbox(context.environment.target);
|
||||
let panel = (toolbox == null) ? null : toolbox.getPanel(id);
|
||||
|
||||
if (!panel) {
|
||||
throw gcli.lookup("profilerNotReady");
|
||||
}
|
||||
|
||||
let profile = panel.getProfileByName(args.name);
|
||||
if (!profile) {
|
||||
throw gcli.lookup("profilerNotFound");
|
||||
}
|
||||
|
||||
panel.sidebar.selectedItem = panel.sidebar.getItemByProfile(profile);
|
||||
}
|
||||
}];
|
@ -1,16 +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";
|
||||
|
||||
module.exports = {
|
||||
L10N_BUNDLE: "chrome://browser/locale/devtools/profiler.properties",
|
||||
|
||||
PROFILER_ENABLED: "devtools.profiler.enabled",
|
||||
SHOW_PLATFORM_DATA: "devtools.profiler.ui.show-platform-data",
|
||||
|
||||
PROFILE_IDLE: 0,
|
||||
PROFILE_RUNNING: 1,
|
||||
PROFILE_COMPLETED: 2
|
||||
};
|
@ -1,411 +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";
|
||||
|
||||
var isJSM = typeof require !== "function";
|
||||
|
||||
// This code is needed because, for whatever reason, mochitest can't
|
||||
// find any requirejs module so we have to load it old school way. :(
|
||||
|
||||
if (isJSM) {
|
||||
var Cu = this["Components"].utils;
|
||||
let XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}).XPCOMUtils;
|
||||
this["loader"] = { lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils) };
|
||||
this["require"] = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
} else {
|
||||
var { Cu } = require("chrome");
|
||||
}
|
||||
|
||||
const { L10N_BUNDLE } = require("devtools/profiler/consts");
|
||||
|
||||
var EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
loader.lazyGetter(this, "L10N", () => new ViewHelpers.L10N(L10N_BUNDLE));
|
||||
|
||||
loader.lazyGetter(this, "gDevTools",
|
||||
() => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools);
|
||||
|
||||
loader.lazyGetter(this, "DebuggerServer",
|
||||
() => Cu.import("resource:///modules/devtools/dbg-server.jsm", {}).DebuggerServer);
|
||||
|
||||
/**
|
||||
* Data structure that contains information that has
|
||||
* to be shared between separate ProfilerController
|
||||
* instances.
|
||||
*/
|
||||
const sharedData = {
|
||||
data: new WeakMap(),
|
||||
controllers: new WeakMap(),
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes a structure representing an individual profile.
|
||||
*/
|
||||
function makeProfile(name, def={}) {
|
||||
if (def.timeStarted == null)
|
||||
def.timeStarted = null;
|
||||
|
||||
if (def.timeEnded == null)
|
||||
def.timeEnded = null;
|
||||
|
||||
return {
|
||||
name: name,
|
||||
timeStarted: def.timeStarted,
|
||||
timeEnded: def.timeEnded,
|
||||
fromConsole: def.fromConsole || false
|
||||
};
|
||||
}
|
||||
|
||||
// Three functions below all operate with sharedData
|
||||
// structure defined above. They should be self-explanatory.
|
||||
|
||||
function addTarget(target) {
|
||||
sharedData.data.set(target, new Map());
|
||||
}
|
||||
|
||||
function getProfiles(target) {
|
||||
return sharedData.data.get(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Object to control the JavaScript Profiler over the remote
|
||||
* debugging protocol.
|
||||
*
|
||||
* @param Target target
|
||||
* A target object as defined in Target.jsm
|
||||
*/
|
||||
function ProfilerController(target) {
|
||||
if (sharedData.controllers.has(target)) {
|
||||
return sharedData.controllers.get(target);
|
||||
}
|
||||
|
||||
this.target = target;
|
||||
this.client = target.client;
|
||||
this.isConnected = false;
|
||||
this.consoleProfiles = [];
|
||||
this.reservedNames = {};
|
||||
|
||||
addTarget(target);
|
||||
|
||||
// Chrome debugging targets have already obtained a reference
|
||||
// to the profiler actor.
|
||||
if (target.chrome) {
|
||||
this.isConnected = true;
|
||||
this.actor = target.form.profilerActor;
|
||||
}
|
||||
|
||||
sharedData.controllers.set(target, this);
|
||||
EventEmitter.decorate(this);
|
||||
};
|
||||
|
||||
ProfilerController.prototype = {
|
||||
target: null,
|
||||
client: null,
|
||||
isConnected: null,
|
||||
consoleProfiles: null,
|
||||
reservedNames: null,
|
||||
|
||||
/**
|
||||
* Return a map of profile results for the current target.
|
||||
*
|
||||
* @return Map
|
||||
*/
|
||||
get profiles() {
|
||||
return getProfiles(this.target);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the profile is currently recording.
|
||||
*
|
||||
* @param object profile
|
||||
* An object made by calling makeProfile function.
|
||||
* @return boolean
|
||||
*/
|
||||
isProfileRecording: function PC_isProfileRecording(profile) {
|
||||
return profile.timeStarted !== null && profile.timeEnded === null;
|
||||
},
|
||||
|
||||
getProfileName: function PC_getProfileName() {
|
||||
let num = 1;
|
||||
let name = L10N.getFormatStr("profiler.profileName", [num]);
|
||||
|
||||
while (this.reservedNames[name]) {
|
||||
num += 1;
|
||||
name = L10N.getFormatStr("profiler.profileName", [num]);
|
||||
}
|
||||
|
||||
this.reservedNames[name] = true;
|
||||
return name;
|
||||
},
|
||||
|
||||
/**
|
||||
* A listener that fires whenever console.profile or console.profileEnd
|
||||
* is called.
|
||||
*
|
||||
* @param string type
|
||||
* Type of a call. Either 'profile' or 'profileEnd'.
|
||||
* @param object data
|
||||
* Event data.
|
||||
*/
|
||||
onConsoleEvent: function (type, data) {
|
||||
let name = data.extra.name;
|
||||
|
||||
let profileStart = () => {
|
||||
if (name && this.profiles.has(name))
|
||||
return;
|
||||
|
||||
// Add profile structure to shared data.
|
||||
let profile = makeProfile(name || this.getProfileName(), {
|
||||
timeStarted: data.extra.currentTime,
|
||||
fromConsole: true
|
||||
});
|
||||
|
||||
this.profiles.set(profile.name, profile);
|
||||
this.consoleProfiles.push(profile.name);
|
||||
this.emit("profileStart", profile);
|
||||
};
|
||||
|
||||
let profileEnd = () => {
|
||||
if (!name && !this.consoleProfiles.length)
|
||||
return;
|
||||
|
||||
if (!name)
|
||||
name = this.consoleProfiles.pop();
|
||||
else
|
||||
this.consoleProfiles.filter((n) => n !== name);
|
||||
|
||||
if (!this.profiles.has(name))
|
||||
return;
|
||||
|
||||
let profile = this.profiles.get(name);
|
||||
if (!this.isProfileRecording(profile))
|
||||
return;
|
||||
|
||||
let profileData = data.extra.profile;
|
||||
profileData.threads = profileData.threads.map((thread) => {
|
||||
let samples = thread.samples.filter((sample) => {
|
||||
return sample.time >= profile.timeStarted;
|
||||
});
|
||||
|
||||
return { samples: samples };
|
||||
});
|
||||
|
||||
profile.timeEnded = data.extra.currentTime;
|
||||
profile.data = profileData;
|
||||
|
||||
this.emit("profileEnd", profile);
|
||||
};
|
||||
|
||||
if (type === "profile")
|
||||
profileStart();
|
||||
|
||||
if (type === "profileEnd")
|
||||
profileEnd();
|
||||
},
|
||||
|
||||
/**
|
||||
* Connects to the client unless we're already connected.
|
||||
*
|
||||
* @param function cb
|
||||
* Function to be called once we're connected. If
|
||||
* the controller is already connected, this function
|
||||
* will be called immediately (synchronously).
|
||||
*/
|
||||
connect: function (cb=function(){}) {
|
||||
if (this.isConnected) {
|
||||
return void cb();
|
||||
}
|
||||
|
||||
// Check if we already have a grip to the listTabs response object
|
||||
// and, if we do, use it to get to the profilerActor. Otherwise,
|
||||
// call listTabs. The problem is that if we call listTabs twice
|
||||
// webconsole tests fail (see bug 872826).
|
||||
|
||||
let register = () => {
|
||||
let data = { events: ["console-api-profiler"] };
|
||||
|
||||
// Check if Gecko Profiler Addon [1] is installed and, if it is,
|
||||
// don't register our own console event listeners. Gecko Profiler
|
||||
// Addon takes care of console.profile and console.profileEnd methods
|
||||
// and we don't want to break it.
|
||||
//
|
||||
// [1] - https://github.com/bgirard/Gecko-Profiler-Addon/
|
||||
|
||||
AddonManager.getAddonByID("jid0-edalmuivkozlouyij0lpdx548bc@jetpack", (addon) => {
|
||||
if (addon && !addon.userDisabled && !addon.softDisabled)
|
||||
return void cb();
|
||||
|
||||
this.request("registerEventNotifications", data, (resp) => {
|
||||
this.client.addListener("eventNotification", (type, resp) => {
|
||||
let toolbox = gDevTools.getToolbox(this.target);
|
||||
if (toolbox == null)
|
||||
return;
|
||||
|
||||
this.onConsoleEvent(resp.subject.action, resp.data);
|
||||
});
|
||||
});
|
||||
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
if (this.target.root) {
|
||||
this.actor = this.target.root.profilerActor;
|
||||
this.isConnected = true;
|
||||
return void register();
|
||||
}
|
||||
|
||||
this.client.listTabs((resp) => {
|
||||
this.actor = resp.profilerActor;
|
||||
this.isConnected = true;
|
||||
register();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds actor and type information to data and sends the request over
|
||||
* the remote debugging protocol.
|
||||
*
|
||||
* @param string type
|
||||
* Method to call on the other side
|
||||
* @param object data
|
||||
* Data to send with the request
|
||||
* @param function cb
|
||||
* A callback function
|
||||
*/
|
||||
request: function (type, data, cb) {
|
||||
data.to = this.actor;
|
||||
data.type = type;
|
||||
this.client.request(data, cb);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the profiler is active.
|
||||
*
|
||||
* @param function cb
|
||||
* Function to be called with a response from the
|
||||
* client. It will be called with two arguments:
|
||||
* an error object (may be null) and a boolean
|
||||
* value indicating if the profiler is active or not.
|
||||
*/
|
||||
isActive: function (cb) {
|
||||
this.request("isActive", {}, (resp) => {
|
||||
cb(resp.error, resp.isActive, resp.currentTime);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new profile and starts the profiler, if needed.
|
||||
*
|
||||
* @param string name
|
||||
* Name of the profile.
|
||||
* @param function cb
|
||||
* Function to be called once the profiler is started
|
||||
* or we get an error. It will be called with a single
|
||||
* argument: an error object (may be null).
|
||||
*/
|
||||
start: function PC_start(name, cb) {
|
||||
if (this.profiles.has(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let profile = makeProfile(name);
|
||||
this.consoleProfiles.push(name);
|
||||
this.profiles.set(name, profile);
|
||||
|
||||
// If profile is already running, no need to do anything.
|
||||
if (this.isProfileRecording(profile)) {
|
||||
return void cb();
|
||||
}
|
||||
|
||||
this.isActive((err, isActive, currentTime) => {
|
||||
if (isActive) {
|
||||
profile.timeStarted = currentTime;
|
||||
return void cb();
|
||||
}
|
||||
|
||||
let params = {
|
||||
entries: 1000000,
|
||||
interval: 1,
|
||||
features: ["js"],
|
||||
};
|
||||
|
||||
this.request("startProfiler", params, (resp) => {
|
||||
if (resp.error) {
|
||||
return void cb(resp.error);
|
||||
}
|
||||
|
||||
profile.timeStarted = 0;
|
||||
cb();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops the profiler. NOTE, that we don't stop the actual
|
||||
* SPS Profiler here. It will be stopped as soon as all
|
||||
* clients disconnect from the profiler actor.
|
||||
*
|
||||
* @param string name
|
||||
* Name of the profile that needs to be stopped.
|
||||
* @param function cb
|
||||
* Function to be called once the profiler is stopped
|
||||
* or we get an error. It will be called with a single
|
||||
* argument: an error object (may be null).
|
||||
*/
|
||||
stop: function PC_stop(name, cb) {
|
||||
if (!this.profiles.has(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let profile = this.profiles.get(name);
|
||||
if (!this.isProfileRecording(profile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.request("getProfile", {}, (resp) => {
|
||||
if (resp.error) {
|
||||
Cu.reportError("Failed to fetch profile data.");
|
||||
return void cb(resp.error, null);
|
||||
}
|
||||
|
||||
let data = resp.profile;
|
||||
profile.timeEnded = resp.currentTime;
|
||||
|
||||
// Filter out all samples that fall out of current
|
||||
// profile's range.
|
||||
|
||||
data.threads = data.threads.map((thread) => {
|
||||
let samples = thread.samples.filter((sample) => {
|
||||
return sample.time >= profile.timeStarted;
|
||||
});
|
||||
|
||||
return { samples: samples };
|
||||
});
|
||||
|
||||
cb(null, data);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup.
|
||||
*/
|
||||
destroy: function PC_destroy() {
|
||||
this.client = null;
|
||||
this.target = null;
|
||||
this.actor = null;
|
||||
}
|
||||
};
|
||||
|
||||
if (isJSM) {
|
||||
var EXPORTED_SYMBOLS = ["ProfilerController"];
|
||||
} else {
|
||||
module.exports = ProfilerController;
|
||||
}
|
@ -1,16 +1,14 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
||||
EXTRA_JS_MODULES.devtools.profiler += [
|
||||
'cleopatra.js',
|
||||
'commands.js',
|
||||
'consts.js',
|
||||
'controller.js',
|
||||
'panel.js',
|
||||
'sidebar.js',
|
||||
'utils/global.js',
|
||||
'utils/shared.js',
|
||||
'utils/tree-model.js',
|
||||
'utils/tree-view.js'
|
||||
]
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
@ -1,600 +1,65 @@
|
||||
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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, Cc, Ci, components } = require("chrome");
|
||||
const {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
|
||||
const {
|
||||
PROFILE_IDLE,
|
||||
PROFILE_RUNNING,
|
||||
PROFILE_COMPLETED,
|
||||
SHOW_PLATFORM_DATA,
|
||||
L10N_BUNDLE
|
||||
} = require("devtools/profiler/consts");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
const { TextEncoder } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
|
||||
var EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
var Cleopatra = require("devtools/profiler/cleopatra");
|
||||
var Sidebar = require("devtools/profiler/sidebar");
|
||||
var ProfilerController = require("devtools/profiler/controller");
|
||||
var { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
loader.lazyRequireGetter(this, "getProfilerConnection",
|
||||
"devtools/profiler/shared", true);
|
||||
loader.lazyRequireGetter(this, "ProfilerFront",
|
||||
"devtools/profiler/shared", true);
|
||||
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
loader.lazyGetter(this, "L10N", () => new ViewHelpers.L10N(L10N_BUNDLE));
|
||||
|
||||
/**
|
||||
* 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 = {};
|
||||
function ProfilerPanel(iframeWindow, toolbox) {
|
||||
this.panelWin = iframeWindow;
|
||||
this._toolbox = toolbox;
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
exports.ProfilerPanel = ProfilerPanel;
|
||||
|
||||
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 import() doc.querySelector("#profiler-import"),
|
||||
};
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
get showPlatformData() {
|
||||
return Services.prefs.getBoolPref(SHOW_PLATFORM_DATA);
|
||||
},
|
||||
|
||||
set showPlatformData(enabled) {
|
||||
Services.prefs.setBoolPref(SHOW_PLATFORM_DATA, enabled);
|
||||
},
|
||||
|
||||
/**
|
||||
* Open a debug connection and, on success, switch to the newly created
|
||||
* profile.
|
||||
* Open is effectively an asynchronous constructor.
|
||||
*
|
||||
* @return Promise
|
||||
* @return object
|
||||
* A promise that is resolved when the Profiler completes opening.
|
||||
*/
|
||||
open: function PP_open() {
|
||||
// Local profiling needs to make the target remote.
|
||||
let target = this.target;
|
||||
let targetPromise = !target.isRemote ? target.makeRemote() : promise.resolve(target);
|
||||
open: Task.async(function*() {
|
||||
let connection = getProfilerConnection(this._toolbox);
|
||||
yield connection.open();
|
||||
|
||||
return targetPromise
|
||||
.then((target) => {
|
||||
let deferred = promise.defer();
|
||||
|
||||
this.controller = new ProfilerController(this.target);
|
||||
this.sidebar = new Sidebar(this.document.querySelector("#profiles-list"));
|
||||
|
||||
this.sidebar.on("save", (_, uid) => {
|
||||
let profile = this.profiles.get(uid);
|
||||
|
||||
if (!profile.data)
|
||||
return void Cu.reportError("Can't save profile because there's no data.");
|
||||
|
||||
this.openFileDialog({ mode: "save", name: profile.name }).then((file) => {
|
||||
if (file)
|
||||
this.saveProfile(file, profile.data);
|
||||
});
|
||||
});
|
||||
|
||||
this.sidebar.on("select", (_, uid) => {
|
||||
let profile = this.profiles.get(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");
|
||||
|
||||
let imp = this.controls.import;
|
||||
imp.addEventListener("click", () => {
|
||||
this.openFileDialog({ mode: "open" }).then((file) => {
|
||||
if (file)
|
||||
this.loadProfile(file);
|
||||
});
|
||||
}, false);
|
||||
imp.removeAttribute("disabled");
|
||||
|
||||
// Import queued profiles.
|
||||
for (let [name, data] of this.controller.profiles) {
|
||||
this.importProfile(name, data.data);
|
||||
}
|
||||
this.panelWin.gToolbox = this._toolbox;
|
||||
this.panelWin.gTarget = this.target;
|
||||
this.panelWin.gFront = new ProfilerFront(connection);
|
||||
yield this.panelWin.startupProfiler();
|
||||
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
deferred.resolve(this);
|
||||
});
|
||||
return this;
|
||||
}),
|
||||
|
||||
this.controller.on("profileEnd", (_, data) => {
|
||||
this.importProfile(data.name, data.data);
|
||||
// DevToolPanel API
|
||||
|
||||
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 Profile
|
||||
*/
|
||||
createProfile: function (name, opts={}) {
|
||||
if (name && this.getProfileByName(name)) {
|
||||
return this.getProfileByName(name);
|
||||
}
|
||||
|
||||
let uid = ++this._uid;
|
||||
let name = name || this.controller.getProfileName();
|
||||
let profile = new Cleopatra(this, {
|
||||
uid: uid,
|
||||
name: name,
|
||||
showPlatformData: this.showPlatformData,
|
||||
external: opts.external
|
||||
});
|
||||
|
||||
this.profiles.set(uid, profile);
|
||||
this.sidebar.addProfile(profile);
|
||||
this.emit("profileCreated", uid);
|
||||
|
||||
return profile;
|
||||
},
|
||||
|
||||
/**
|
||||
* Imports profile data
|
||||
*
|
||||
* @param string name, new profile name
|
||||
* @param object data, profile data to import
|
||||
* @param object opts, (optional) if property 'external' is found
|
||||
* Cleopatra will hide arrow buttons.
|
||||
*
|
||||
* @return Profile
|
||||
*/
|
||||
importProfile: function (name, data, opts={}) {
|
||||
let profile = this.createProfile(name, { external: opts.external });
|
||||
profile.isStarted = false;
|
||||
profile.isFinished = true;
|
||||
profile.data = data;
|
||||
profile.parse(data, () => this.emit("parsed"));
|
||||
|
||||
this.sidebar.setProfileState(profile, PROFILE_COMPLETED);
|
||||
if (!this.sidebar.selectedItem)
|
||||
this.sidebar.selectedItem = this.sidebar.getItemByProfile(profile);
|
||||
|
||||
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");
|
||||
});
|
||||
get target() this._toolbox.target,
|
||||
|
||||
destroy: Task.async(function*() {
|
||||
// Make sure this panel is not already destroyed.
|
||||
if (this._destroyed) {
|
||||
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) {
|
||||
let { browserWindow: win, document: doc } = this;
|
||||
let { uri, line, isChrome } = data;
|
||||
let deferred = promise.defer();
|
||||
|
||||
if (isChrome) {
|
||||
return void win.gViewSourceUtils.viewSource(uri, null, doc, line);
|
||||
}
|
||||
|
||||
let showSource = ({ DebuggerView }) => {
|
||||
if (DebuggerView.Sources.containsValue(uri)) {
|
||||
DebuggerView.setEditorLocation(uri, line).then(deferred.resolve);
|
||||
}
|
||||
// XXX: What to do if the source isn't present in the Debugger?
|
||||
// Switch back to the Profiler panel and viewSource()?
|
||||
}
|
||||
|
||||
// If the Debugger was already open, switch to it and try to show the
|
||||
// source immediately. Otherwise, initialize it and wait for the sources
|
||||
// to be added first.
|
||||
let toolbox = gDevTools.getToolbox(this.target);
|
||||
let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger");
|
||||
toolbox.selectTool("jsdebugger").then(({ panelWin: dbg }) => {
|
||||
if (debuggerAlreadyOpen) {
|
||||
showSource(dbg);
|
||||
} else {
|
||||
dbg.once(dbg.EVENTS.SOURCES_ADDED, () => showSource(dbg));
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens a normal file dialog.
|
||||
*
|
||||
* @params object opts, (optional) property 'mode' can be used to
|
||||
* specify which dialog to open. Can be either
|
||||
* 'save' or 'open' (default is 'open').
|
||||
* @return promise
|
||||
*/
|
||||
openFileDialog: function (opts={}) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let picker = Ci.nsIFilePicker;
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(picker);
|
||||
let { name, mode } = opts;
|
||||
let save = mode === "save";
|
||||
let title = L10N.getStr(save ? "profiler.saveFileAs" : "profiler.openFile");
|
||||
|
||||
fp.init(this.window, title, save ? picker.modeSave : picker.modeOpen);
|
||||
fp.appendFilter("JSON", "*.json");
|
||||
fp.appendFilters(picker.filterText | picker.filterAll);
|
||||
|
||||
if (save)
|
||||
fp.defaultString = (name || "profile") + ".json";
|
||||
|
||||
fp.open((result) => {
|
||||
deferred.resolve(result === picker.returnCancel ? null : fp.file);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves profile data to disk
|
||||
*
|
||||
* @param File file
|
||||
* @param object data
|
||||
*
|
||||
* @return promise
|
||||
*/
|
||||
saveProfile: function (file, data) {
|
||||
let encoder = new TextEncoder();
|
||||
let buffer = encoder.encode(JSON.stringify({ profile: data }, null, " "));
|
||||
let opts = { tmpPath: file.path + ".tmp" };
|
||||
|
||||
return OS.File.writeAtomic(file.path, buffer, opts);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reads profile data from disk
|
||||
*
|
||||
* @param File file
|
||||
* @return promise
|
||||
*/
|
||||
loadProfile: function (file) {
|
||||
let deferred = promise.defer();
|
||||
let ch = NetUtil.newChannel(file);
|
||||
ch.contentType = "application/json";
|
||||
|
||||
NetUtil.asyncFetch(ch, (input, status) => {
|
||||
if (!components.isSuccessCode(status)) throw new Error(status);
|
||||
|
||||
let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
conv.charset = "UTF-8";
|
||||
|
||||
let data = NetUtil.readInputStreamToString(input, input.available());
|
||||
data = conv.ConvertToUnicode(data);
|
||||
this.importProfile(file.leafName, JSON.parse(data).profile, { external: true });
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
yield this.panelWin.shutdownProfiler();
|
||||
this.emit("destroyed");
|
||||
}
|
||||
this._destroyed = true;
|
||||
})
|
||||
};
|
||||
|
||||
module.exports = ProfilerPanel;
|
||||
|
232
browser/devtools/profiler/profiler.js
Normal file
232
browser/devtools/profiler/profiler.js
Normal file
@ -0,0 +1,232 @@
|
||||
/* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
devtools.lazyRequireGetter(this, "Services");
|
||||
devtools.lazyRequireGetter(this, "promise");
|
||||
devtools.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
devtools.lazyRequireGetter(this, "DevToolsUtils",
|
||||
"devtools/toolkit/DevToolsUtils");
|
||||
devtools.lazyRequireGetter(this, "FramerateFront",
|
||||
"devtools/server/actors/framerate", true);
|
||||
|
||||
devtools.lazyRequireGetter(this, "L10N",
|
||||
"devtools/profiler/global", true);
|
||||
devtools.lazyRequireGetter(this, "CATEGORIES",
|
||||
"devtools/profiler/global", true);
|
||||
devtools.lazyRequireGetter(this, "CATEGORY_MAPPINGS",
|
||||
"devtools/profiler/global", true);
|
||||
devtools.lazyRequireGetter(this, "ThreadNode",
|
||||
"devtools/profiler/tree-model", true);
|
||||
devtools.lazyRequireGetter(this, "CallView",
|
||||
"devtools/profiler/tree-view", true);
|
||||
|
||||
devtools.lazyImporter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
devtools.lazyImporter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
devtools.lazyImporter(this, "LineGraphWidget",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
devtools.lazyImporter(this, "BarGraphWidget",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
devtools.lazyImporter(this, "CanvasGraphUtils",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
devtools.lazyImporter(this, "SideMenuWidget",
|
||||
"resource:///modules/devtools/SideMenuWidget.jsm");
|
||||
|
||||
const RECORDING_DATA_DISPLAY_DELAY = 10; // ms
|
||||
const FRAMERATE_CALC_INTERVAL = 16; // ms
|
||||
const FRAMERATE_GRAPH_HEIGHT = 60; // px
|
||||
const CATEGORIES_GRAPH_HEIGHT = 60; // px
|
||||
const CATEGORIES_GRAPH_MIN_BARS_WIDTH = 3; // px
|
||||
const CALL_VIEW_FOCUS_EVENTS_DRAIN = 10; // ms
|
||||
const GRAPH_SCROLL_EVENTS_DRAIN = 50; // ms
|
||||
const GRAPH_ZOOM_MIN_TIMESPAN = 20; // ms
|
||||
|
||||
// This identifier string is used to tentatively ascertain whether or not
|
||||
// a JSON loaded from disk is actually something generated by this tool.
|
||||
// It isn't, of course, a definitive verification, but a Good Enough™
|
||||
// approximation before continuing the import. Don't localize this.
|
||||
const PROFILE_SERIALIZER_IDENTIFIER = "Recorded Performance Data";
|
||||
const PROFILE_SERIALIZER_VERSION = 1;
|
||||
|
||||
// The panel's window global is an EventEmitter firing the following events:
|
||||
const EVENTS = {
|
||||
// When a recording is started or stopped, via the `stopwatch` button, or
|
||||
// when `console.profile` and `console.profileEnd` is invoked.
|
||||
RECORDING_STARTED: "Profiler:RecordingStarted",
|
||||
RECORDING_ENDED: "Profiler:RecordingEnded",
|
||||
|
||||
// When a recording is abruptly ended, either because the built-in profiler
|
||||
// module is stopped by a third party, or because the recordings list is
|
||||
// cleared while there's one in progress.
|
||||
RECORDING_LOST: "Profiler:RecordingCancelled",
|
||||
|
||||
// When a recording is displayed in the ProfileView.
|
||||
RECORDING_DISPLAYED: "Profiler:RecordingDisplayed",
|
||||
|
||||
// When a new tab is spawned in the ProfileView from a graphs selection.
|
||||
TAB_SPAWNED_FROM_SELECTION: "Profiler:TabSpawnedFromSelection",
|
||||
|
||||
// When a new tab is spawned in the ProfileView from a node in the tree.
|
||||
TAB_SPAWNED_FROM_FRAME_NODE: "Profiler:TabSpawnedFromFrameNode",
|
||||
|
||||
// When different panels in the ProfileView are shown.
|
||||
EMPTY_NOTICE_SHOWN: "Profiler:EmptyNoticeShown",
|
||||
RECORDING_NOTICE_SHOWN: "Profiler:RecordingNoticeShown",
|
||||
LOADING_NOTICE_SHOWN: "Profiler:LoadingNoticeShown",
|
||||
TABBED_BROWSER_SHOWN: "Profiler:TabbedBrowserShown",
|
||||
|
||||
// When a source is shown in the JavaScript Debugger at a specific location.
|
||||
SOURCE_SHOWN_IN_JS_DEBUGGER: "Profiler:SourceShownInJsDebugger",
|
||||
SOURCE_NOT_FOUND_IN_JS_DEBUGGER: "Profiler:SourceNotFoundInJsDebugger"
|
||||
};
|
||||
|
||||
/**
|
||||
* The current target and the profiler connection, set by this tool's host.
|
||||
*/
|
||||
let gToolbox, gTarget, gFront;
|
||||
|
||||
/**
|
||||
* Initializes the profiler controller and views.
|
||||
*/
|
||||
let startupProfiler = Task.async(function*() {
|
||||
yield promise.all([
|
||||
PrefObserver.register(),
|
||||
EventsHandler.initialize(),
|
||||
RecordingsListView.initialize(),
|
||||
ProfileView.initialize()
|
||||
]);
|
||||
|
||||
// Profiles may have been created before this tool was opened, e.g. via
|
||||
// `console.profile` and `console.profileEnd(). Populate the UI with them.
|
||||
for (let recordingData of gFront.finishedConsoleRecordings) {
|
||||
let profileLabel = recordingData.profilerData.profileLabel;
|
||||
let recordingItem = RecordingsListView.addEmptyRecording(profileLabel);
|
||||
RecordingsListView.customizeRecording(recordingItem, recordingData);
|
||||
}
|
||||
for (let { profileLabel } of gFront.pendingConsoleRecordings) {
|
||||
RecordingsListView.handleRecordingStarted(profileLabel);
|
||||
}
|
||||
|
||||
// Select the first recording, if available.
|
||||
RecordingsListView.selectedIndex = 0;
|
||||
});
|
||||
|
||||
/**
|
||||
* Destroys the profiler controller and views.
|
||||
*/
|
||||
let shutdownProfiler = Task.async(function*() {
|
||||
yield promise.all([
|
||||
PrefObserver.unregister(),
|
||||
EventsHandler.destroy(),
|
||||
RecordingsListView.destroy(),
|
||||
ProfileView.destroy()
|
||||
]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Observes pref changes on the devtools.profiler branch and triggers the
|
||||
* required frontend modifications.
|
||||
*/
|
||||
let PrefObserver = {
|
||||
register: function() {
|
||||
this.branch = Services.prefs.getBranch("devtools.profiler.");
|
||||
this.branch.addObserver("", this, false);
|
||||
},
|
||||
unregister: function() {
|
||||
this.branch.removeObserver("", this);
|
||||
},
|
||||
observe: function(subject, topic, pref) {
|
||||
Prefs.refresh();
|
||||
|
||||
if (pref == "ui.show-platform-data") {
|
||||
RecordingsListView.forceSelect(RecordingsListView.selectedItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Functions handling target-related lifetime events.
|
||||
*/
|
||||
let EventsHandler = {
|
||||
/**
|
||||
* Listen for events emitted by the current tab target.
|
||||
*/
|
||||
initialize: function() {
|
||||
this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this);
|
||||
this._onConsoleProfileEnd = this._onConsoleProfileEnd.bind(this);
|
||||
|
||||
gFront.on("profile", this._onConsoleProfileStart);
|
||||
gFront.on("profileEnd", this._onConsoleProfileEnd);
|
||||
gFront.on("profiler-unexpectedly-stopped", this._onProfilerDeactivated);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove events emitted by the current tab target.
|
||||
*/
|
||||
destroy: function() {
|
||||
gFront.off("profile", this._onConsoleProfileStart);
|
||||
gFront.off("profileEnd", this._onConsoleProfileEnd);
|
||||
gFront.off("profiler-unexpectedly-stopped", this._onProfilerDeactivated);
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever `console.profile` is called.
|
||||
*
|
||||
* @param string profileLabel
|
||||
* The provided string argument if available, undefined otherwise.
|
||||
*/
|
||||
_onConsoleProfileStart: function(event, profileLabel) {
|
||||
RecordingsListView.handleRecordingStarted(profileLabel);
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever `console.profileEnd` is called.
|
||||
*
|
||||
* @param object recordingData
|
||||
* The profiler and refresh driver ticks data received from the front.
|
||||
*/
|
||||
_onConsoleProfileEnd: function(event, recordingData) {
|
||||
RecordingsListView.handleRecordingEnded(recordingData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever the built-in profiler module is deactivated.
|
||||
* @see ProfilerConnection.prototype._onProfilerUnexpectedlyStopped
|
||||
*/
|
||||
_onProfilerDeactivated: function() {
|
||||
RecordingsListView.removeForPredicate(e => e.isRecording);
|
||||
RecordingsListView.handleRecordingCancelled();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shortcuts for accessing various profiler preferences.
|
||||
*/
|
||||
const Prefs = new ViewHelpers.Prefs("devtools.profiler", {
|
||||
showPlatformData: ["Bool", "ui.show-platform-data"]
|
||||
});
|
||||
|
||||
/**
|
||||
* Convenient way of emitting events from the panel window.
|
||||
*/
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
/**
|
||||
* DOM query helpers.
|
||||
*/
|
||||
function $(selector, target = document) {
|
||||
return target.querySelector(selector);
|
||||
}
|
||||
function $$(selector, target = document) {
|
||||
return target.querySelectorAll(selector);
|
||||
}
|
@ -1,52 +1,129 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/profiler.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css"?>
|
||||
|
||||
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/profiler.css" type="text/css"?>
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % profilerDTD SYSTEM "chrome://browser/locale/devtools/profiler.dtd">
|
||||
<!ENTITY % profilerDTD SYSTEM "chrome://browser/locale/devtools/profiler.dtd">
|
||||
%profilerDTD;
|
||||
]>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script src="chrome://browser/content/devtools/theme-switching.js"/>
|
||||
<script type="application/javascript" src="profiler.js"/>
|
||||
<script type="application/javascript" src="ui-recordings.js"/>
|
||||
<script type="application/javascript" src="ui-profile.js"/>
|
||||
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://browser/content/devtools/theme-switching.js"/>
|
||||
<script type="text/javascript" src="sidebar.js"/>
|
||||
<box flex="1" id="profiler-chrome"
|
||||
class="devtools-responsive-container theme-body">
|
||||
<vbox class="profiler-sidebar theme-sidebar">
|
||||
<toolbar class="devtools-toolbar">
|
||||
<hbox id="profiler-controls">
|
||||
<toolbarbutton id="profiler-start"
|
||||
tooltiptext="&startProfiler.tooltip;"
|
||||
<hbox class="theme-body" flex="1">
|
||||
<vbox id="recordings-pane">
|
||||
<toolbar id="recordings-toolbar"
|
||||
class="devtools-toolbar">
|
||||
<hbox id="recordings-controls"
|
||||
class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="record-button"
|
||||
class="devtools-toolbarbutton"
|
||||
disabled="true"/>
|
||||
<toolbarbutton id="profiler-import"
|
||||
oncommand="RecordingsListView._onRecordButtonClick()"
|
||||
tooltiptext="&profilerUI.recordButton.tooltip;"/>
|
||||
<toolbarbutton id="import-button"
|
||||
class="devtools-toolbarbutton"
|
||||
disabled="true"
|
||||
label="&importProfile.label;"/>
|
||||
oncommand="RecordingsListView._onImportButtonClick()"
|
||||
label="&profilerUI.importButton;"/>
|
||||
<toolbarbutton id="clear-button"
|
||||
class="devtools-toolbarbutton"
|
||||
oncommand="RecordingsListView._onClearButtonClick()"
|
||||
label="&profilerUI.clearButton;"/>
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
<vbox id="profiles-list" flex="1">
|
||||
</vbox>
|
||||
<vbox id="recordings-list" flex="1"/>
|
||||
</vbox>
|
||||
|
||||
<splitter class="devtools-side-splitter devtools-invisible-splitter"/>
|
||||
<deck id="profile-pane"
|
||||
class="devtools-responsive-container"
|
||||
flex="1">
|
||||
<hbox id="empty-notice"
|
||||
class="notice-container"
|
||||
align="center"
|
||||
pack="center"
|
||||
flex="1">
|
||||
<label value="&profilerUI.emptyNotice1;"/>
|
||||
<button id="profiling-notice-button"
|
||||
class="devtools-toolbarbutton"
|
||||
standalone="true"
|
||||
oncommand="RecordingsListView._onRecordButtonClick()"/>
|
||||
<label value="&profilerUI.emptyNotice2;"/>
|
||||
</hbox>
|
||||
|
||||
<vbox flex="1" id="profiler-report">
|
||||
<!-- Example:
|
||||
<iframe id="profiler-cleo-1"
|
||||
src="devtools/cleopatra.html" flex="1"></iframe>
|
||||
-->
|
||||
<hbox id="recording-notice"
|
||||
class="notice-container"
|
||||
align="center"
|
||||
pack="center"
|
||||
flex="1">
|
||||
<label value="&profilerUI.stopNotice1;"/>
|
||||
<button id="profiling-notice-button"
|
||||
class="devtools-toolbarbutton"
|
||||
standalone="true"
|
||||
checked="true"
|
||||
oncommand="RecordingsListView._onRecordButtonClick()"/>
|
||||
<label value="&profilerUI.stopNotice2;"/>
|
||||
</hbox>
|
||||
|
||||
<hbox id="loading-notice"
|
||||
class="notice-container"
|
||||
align="center"
|
||||
pack="center"
|
||||
flex="1">
|
||||
<label value="&profilerUI.loadingNotice;"/>
|
||||
</hbox>
|
||||
|
||||
<tabbox id="profile-content"
|
||||
class="theme-sidebar devtools-sidebar-tabs"
|
||||
flex="1">
|
||||
<hbox>
|
||||
<tabs/>
|
||||
<button id="profile-newtab-button"
|
||||
tooltiptext="&profilerUI.newtab.tooltiptext;"/>
|
||||
</hbox>
|
||||
<tabpanels flex="1"/>
|
||||
</tabbox>
|
||||
</deck>
|
||||
</hbox>
|
||||
|
||||
<template>
|
||||
<!-- Template for a tab inside the #profile-content tabbox. -->
|
||||
<tab id="profile-content-tab-template" covered="true">
|
||||
<label class="tab-title-label"/>
|
||||
</tab>
|
||||
|
||||
<!-- Template for a panel inside the #profile-content tabbox. -->
|
||||
<tabpanel id="profile-content-tabpanel-template">
|
||||
<vbox class="framerate"/>
|
||||
<vbox class="categories"/>
|
||||
<vbox class="call-tree" flex="1">
|
||||
<hbox class="call-tree-headers-container">
|
||||
<label class="plain call-tree-header"
|
||||
type="duration"
|
||||
crop="end"
|
||||
value="&profilerUI.table.duration;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="percentage"
|
||||
crop="end"
|
||||
value="&profilerUI.table.percentage;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="invocations"
|
||||
crop="end"
|
||||
value="&profilerUI.table.invocations;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="function"
|
||||
crop="end"
|
||||
value="&profilerUI.table.function;"/>
|
||||
</hbox>
|
||||
<vbox class="call-tree-cells-container" flex="1"/>
|
||||
</vbox>
|
||||
</box>
|
||||
</tabpanel>
|
||||
</template>
|
||||
|
||||
</window>
|
||||
|
@ -1,127 +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";
|
||||
|
||||
let { Cu } = require("chrome");
|
||||
let EventEmitter = require("devtools/toolkit/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));
|
||||
|
||||
let stopProfilingString = L10N.getStr("profiler.stopProfilerString");
|
||||
let startProfilingString = L10N.getStr("profiler.startProfilerString");
|
||||
|
||||
function Sidebar(el) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.document = el.ownerDocument;
|
||||
this.widget = new SideMenuWidget(el, { showArrows: true });
|
||||
this.emptyText = L10N.getStr("profiler.sidebarNotice");
|
||||
|
||||
this.widget.addEventListener("select", (ev) => {
|
||||
if (!ev.detail)
|
||||
return;
|
||||
|
||||
this.emit("select", parseInt(ev.detail.value, 10));
|
||||
});
|
||||
}
|
||||
|
||||
Sidebar.prototype = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Adds a new item for a profile to the sidebar. Markup
|
||||
* example:
|
||||
*
|
||||
* <vbox id="profile-1" class="profiler-sidebar-item">
|
||||
* <h3>Profile 1</h3>
|
||||
* <hbox>
|
||||
* <span flex="1">Completed</span>
|
||||
* <a>Save</a>
|
||||
* </hbox>
|
||||
* </vbox>
|
||||
*
|
||||
*/
|
||||
addProfile: function (profile) {
|
||||
let doc = this.document;
|
||||
let vbox = doc.createElement("vbox");
|
||||
let hbox = doc.createElement("hbox");
|
||||
let h3 = doc.createElement("h3");
|
||||
let span = doc.createElement("span");
|
||||
let save = doc.createElement("a");
|
||||
|
||||
vbox.id = "profile-" + profile.uid;
|
||||
vbox.className = "profiler-sidebar-item";
|
||||
|
||||
h3.textContent = profile.name;
|
||||
span.setAttribute("flex", 1);
|
||||
span.textContent = L10N.getStr("profiler.stateIdle");
|
||||
|
||||
save.textContent = L10N.getStr("profiler.save");
|
||||
save.addEventListener("click", (ev) => {
|
||||
ev.preventDefault();
|
||||
this.emit("save", profile.uid);
|
||||
});
|
||||
|
||||
hbox.appendChild(span);
|
||||
hbox.appendChild(save);
|
||||
|
||||
vbox.appendChild(h3);
|
||||
vbox.appendChild(hbox);
|
||||
|
||||
this.push([vbox, profile.uid], {
|
||||
attachment: {
|
||||
name: profile.name,
|
||||
state: PROFILE_IDLE
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getElementByProfile: function (profile) {
|
||||
return this.document.querySelector("#profile-" + profile.uid);
|
||||
},
|
||||
|
||||
getItemByProfile: function (profile) {
|
||||
return this.getItemByValue(profile.uid.toString());
|
||||
},
|
||||
|
||||
setProfileState: function (profile, state) {
|
||||
let item = this.getItemByProfile(profile);
|
||||
let doc = this.document;
|
||||
let label = item.target.querySelector(".profiler-sidebar-item > hbox > span");
|
||||
let toggleButton = doc.getElementById("profiler-start");
|
||||
|
||||
switch (state) {
|
||||
case PROFILE_IDLE:
|
||||
item.target.setAttribute("state", "idle");
|
||||
label.textContent = L10N.getStr("profiler.stateIdle");
|
||||
break;
|
||||
case PROFILE_RUNNING:
|
||||
item.target.setAttribute("state", "running");
|
||||
label.textContent = L10N.getStr("profiler.stateRunning");
|
||||
toggleButton.setAttribute("tooltiptext",stopProfilingString);
|
||||
break;
|
||||
case PROFILE_COMPLETED:
|
||||
item.target.setAttribute("state", "completed");
|
||||
label.textContent = L10N.getStr("profiler.stateCompleted");
|
||||
toggleButton.setAttribute("tooltiptext",startProfilingString);
|
||||
break;
|
||||
default: // Wrong state, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
item.attachment.state = state;
|
||||
this.emit("stateChanged", item);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Sidebar;
|
@ -1,24 +1,107 @@
|
||||
[DEFAULT]
|
||||
skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
|
||||
# Disabled globally due to crashes/timeouts on all platforms (bug 973974)
|
||||
skip-if = true # Overrides the e10s case above.
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
doc_simple-test.html
|
||||
head.js
|
||||
mock_console_api.html
|
||||
mock_profiler_bug_834878_page.html
|
||||
mock_profiler_bug_834878_script.js
|
||||
|
||||
[browser_profiler_bug_834878_source_buttons.js]
|
||||
[browser_profiler_bug_855244_multiple_tabs.js]
|
||||
[browser_profiler_cmd.js]
|
||||
[browser_profiler_console_api.js]
|
||||
[browser_profiler_console_api_content.js]
|
||||
[browser_profiler_console_api_mixed.js]
|
||||
[browser_profiler_console_api_named.js]
|
||||
[browser_profiler_controller.js]
|
||||
[browser_profiler_escape.js]
|
||||
[browser_profiler_gecko_data.js]
|
||||
[browser_profiler_io.js]
|
||||
[browser_profiler_remote.js]
|
||||
[browser_profiler_run.js]
|
||||
[browser_profiler_aaa_run_first_leaktest.js]
|
||||
[browser_profiler_categories.js]
|
||||
[browser_profiler_console-record-01.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_console-record-02.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_console-record-03.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_console-record-04.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_console-record-05.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_console-record-06.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_console-record-07.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_console-record-08.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_console-record-09.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_content-check.js]
|
||||
[browser_profiler_data-massaging-01.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_data-massaging-02.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_data-samples.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_gecko-pref-changed.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_jump-to-debugger-01.js]
|
||||
[browser_profiler_jump-to-debugger-02.js]
|
||||
[browser_profiler_profile-deselection.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_profile-view-events.js]
|
||||
[browser_profiler_recording-cancelled.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_recording-selected-01.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_recording-selected-02.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_recording-utils.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_recordings-clear.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_recordings-io-01.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_recordings-io-02.js]
|
||||
[browser_profiler_recordings-io-03.js]
|
||||
[browser_profiler_shared-connection-01.js]
|
||||
[browser_profiler_shared-connection-02.js]
|
||||
[browser_profiler_shared-connection-03.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_shared-connection-04.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_shared-front-01.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_shared-front-02.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_shared-front-03.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_shared-front-04.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_shared-front-05.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_shared-front-06.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_simple-record-01.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_simple-record-02.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_simple-record-03.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_sudden-deactivation-01.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_sudden-deactivation-02.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_tabbed-browser-01.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_tabbed-browser-02.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_tabbed-browser-03.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_tabbed-browser-add-remove-01.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_tabbed-browser-add-remove-02.js]
|
||||
skip-if = true # Bug 1047124
|
||||
[browser_profiler_tree-abstract-01.js]
|
||||
[browser_profiler_tree-abstract-02.js]
|
||||
[browser_profiler_tree-abstract-03.js]
|
||||
[browser_profiler_tree-frame-node.js]
|
||||
[browser_profiler_tree-model-01.js]
|
||||
[browser_profiler_tree-model-02.js]
|
||||
[browser_profiler_tree-model-03.js]
|
||||
[browser_profiler_tree-model-04.js]
|
||||
[browser_profiler_tree-view-01.js]
|
||||
[browser_profiler_tree-view-02.js]
|
||||
[browser_profiler_tree-view-03.js]
|
||||
[browser_profiler_tree-view-04.js]
|
||||
[browser_profiler_tree-view-05.js]
|
||||
[browser_profiler_tree-view-06.js]
|
||||
[browser_profiler_tree-view-07.js]
|
||||
|
@ -0,0 +1,22 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler leaks on initialization and sudden destruction.
|
||||
* You can also use this initialization format as a template for other tests.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
|
||||
ok(target, "Should have a target available.");
|
||||
ok(debuggee, "Should have a debuggee available.");
|
||||
ok(panel, "Should have a panel available.");
|
||||
|
||||
ok(panel.panelWin.gToolbox, "Should have a toolbox reference on the panel window.");
|
||||
ok(panel.panelWin.gTarget, "Should have a target reference on the panel window.");
|
||||
ok(panel.panelWin.gFront, "Should have a front reference on the panel window.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -1,33 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const BASE = "http://example.com/browser/browser/devtools/profiler/test/";
|
||||
const URL = BASE + "mock_profiler_bug_834878_page.html";
|
||||
const SCRIPT = BASE + "mock_profiler_bug_834878_script.js";
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function onSetUp(tab, browser, panel) {
|
||||
let data = { uri: SCRIPT, line: 5, isChrome: false };
|
||||
|
||||
panel.displaySource(data).then(function onOpen() {
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let dbg = gDevTools.getToolbox(target).getPanel("jsdebugger");
|
||||
let view = dbg.panelWin.DebuggerView;
|
||||
|
||||
is(view.Sources.selectedValue, data.uri, "URI is different");
|
||||
is(view.editor.getCursor().line, data.line - 1, "Line is different");
|
||||
|
||||
// Test the case where script is already loaded.
|
||||
view.editor.setCursor({ line: 1, ch: 1 });
|
||||
gDevTools.showToolbox(target, "jsprofiler").then(function () {
|
||||
panel.displaySource(data).then(function onOpenAgain() {
|
||||
is(view.editor.getCursor().line, data.line - 1,
|
||||
"Line is different");
|
||||
tearDown(tab);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,103 +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 gTab1, gPanel1;
|
||||
let gTab2, gPanel2;
|
||||
|
||||
// Tests that you can run the profiler in multiple tabs at the same
|
||||
// time and that closing the debugger panel in one tab doesn't lock
|
||||
// profilers in other tabs.
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
gTab1 = gTab2 = gPanel1 = gPanel2 = null;
|
||||
});
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
openTwoTabs()
|
||||
.then(startTwoProfiles)
|
||||
.then(stopFirstProfile)
|
||||
.then(stopSecondProfile)
|
||||
.then(closeTabs)
|
||||
.then(openTwoTabs)
|
||||
.then(startTwoProfiles)
|
||||
.then(closeFirstPanel)
|
||||
.then(stopSecondProfile)
|
||||
.then(closeTabs)
|
||||
.then(finish);
|
||||
}
|
||||
|
||||
function openTwoTabs() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
setUp(URL, (tab, browser, panel) => {
|
||||
gTab1 = tab;
|
||||
gPanel1 = panel;
|
||||
|
||||
loadTab(URL, (tab, browser) => {
|
||||
gTab2 = tab;
|
||||
openProfiler(tab, () => {
|
||||
let target = TargetFactory.forTab(tab);
|
||||
gPanel2 = gDevTools.getToolbox(target).getPanel("jsprofiler");
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function startTwoProfiles() {
|
||||
let deferred = promise.defer();
|
||||
gPanel1.controller.start("Profile 1", (err) => {
|
||||
ok(!err, "Profile in tab 1 started without errors");
|
||||
gPanel2.controller.start("Profile 1", (err) => {
|
||||
ok(!err, "Profile in tab 2 started without errors");
|
||||
gPanel1.controller.isActive((err, isActive) => {
|
||||
ok(isActive, "Profiler is active");
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function stopFirstProfile() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
gPanel1.controller.stop("Profile 1", (err, data) => {
|
||||
ok(!err, "Profile in tab 1 stopped without errors");
|
||||
ok(data, "Profile in tab 1 returned some data");
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function stopSecondProfile() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
gPanel2.controller.stop("Profile 1", (err, data) => {
|
||||
ok(!err, "Profile in tab 2 stopped without errors");
|
||||
ok(data, "Profile in tab 2 returned some data");
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function closeTabs() {
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
}
|
||||
|
||||
function closeFirstPanel() {
|
||||
let target = TargetFactory.forTab(gTab1);
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
return toolbox.destroy;
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler categories are mapped correctly.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
let global = devtools.require("devtools/profiler/global");
|
||||
let l10n = global.L10N;
|
||||
let categories = global.CATEGORIES;
|
||||
let mappings = global.CATEGORY_MAPPINGS;
|
||||
let count = categories.length;
|
||||
|
||||
ok(count,
|
||||
"Should have a non-empty list of categories available.");
|
||||
|
||||
ok(categories.find(e => e.ordinal == count - 1),
|
||||
"The maximum category ordinal is the equal to the categories count.");
|
||||
|
||||
is(categories.reduce((a, b) => a + b.ordinal, 0), count * (count - 1) / 2,
|
||||
"There is an ordinal for every category in the list.");
|
||||
|
||||
is(categories.filter((e, i, self) => self.find(e => e.ordinal == i)).length, count,
|
||||
"All categories have unique ordinals.");
|
||||
|
||||
ok(!categories.some(e => !e.color),
|
||||
"All categories have an associated color.");
|
||||
|
||||
ok(!categories.some(e => !e.label),
|
||||
"All categories have an associated label.");
|
||||
|
||||
ok(!categories.some(e => e.label != l10n.getStr("category." + e.abbrev)),
|
||||
"All categories have a correctly localized label.");
|
||||
|
||||
ok(!Object.keys(mappings).some(e => !Number.isInteger(Math.log2(e))),
|
||||
"All bitmask mappings keys are powers of 2.");
|
||||
|
||||
ok(!Object.keys(mappings).some(e => categories.indexOf(mappings[e]) == -1),
|
||||
"All bitmask mappings point to a category.");
|
||||
|
||||
finish();
|
||||
}
|
@ -1,110 +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 gTarget, gPanel, gOptions;
|
||||
|
||||
function cmd(typed, expected="", waitforEvent=null) {
|
||||
let eventPromise;
|
||||
if (waitforEvent == null) {
|
||||
eventPromise = promise.resolve();
|
||||
}
|
||||
else {
|
||||
let deferred = promise.defer();
|
||||
gPanel.once(waitforEvent, () => { deferred.resolve(); });
|
||||
eventPromise = deferred.promise;
|
||||
}
|
||||
|
||||
let commandPromise = helpers.audit(gOptions, [{
|
||||
setup: typed,
|
||||
exec: { output: expected }
|
||||
}]);
|
||||
|
||||
return promise.all([ commandPromise, eventPromise ]);
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
helpers.addTabWithToolbar(URL, function (options) {
|
||||
gOptions = options;
|
||||
gTarget = options.target;
|
||||
|
||||
return gDevTools.showToolbox(options.target, "jsprofiler")
|
||||
.then(setupGlobals)
|
||||
.then(testProfilerStart)
|
||||
.then(testProfilerList)
|
||||
.then(testProfilerStop)
|
||||
// We need to call this test twice to make sure there are no
|
||||
// errors when executing 'profiler close' on a closed
|
||||
// toolbox. See bug 863636 for more info.
|
||||
.then(testProfilerClose)
|
||||
.then(testProfilerClose);
|
||||
}).then(finishUp, helpers.handleError);
|
||||
}
|
||||
|
||||
function setupGlobals() {
|
||||
let deferred = promise.defer();
|
||||
gPanel = gDevTools.getToolbox(gTarget).getPanel("jsprofiler");
|
||||
deferred.resolve();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testProfilerStart() {
|
||||
let expected = gcli.lookup("profilerStarted2");
|
||||
return cmd("profiler start", expected, "started").then(() => {
|
||||
is(gPanel.profiles.size, 1, "There is a new profile");
|
||||
is(gPanel.getProfileByName("Profile 1"), gPanel.recordingProfile, "Recording profile is OK");
|
||||
ok(!gPanel.activeProfile, "There's no active profile yet");
|
||||
return cmd("profiler start", gcli.lookup("profilerAlreadyStarted2"));
|
||||
});
|
||||
}
|
||||
|
||||
function testProfilerList() {
|
||||
return cmd("profiler list", /^.*Profile\s1\s\*.*$/);
|
||||
}
|
||||
|
||||
function testProfilerStop() {
|
||||
return cmd("profiler stop", gcli.lookup("profilerStopped"), "stopped").then(() => {
|
||||
is(gPanel.activeProfile, gPanel.getProfileByName("Profile 1"), "Active profile is OK");
|
||||
ok(!gPanel.recordingProfile, "There's no recording profile");
|
||||
return cmd("profiler stop", gcli.lookup("profilerNotStarted3"));
|
||||
});
|
||||
}
|
||||
|
||||
function testProfilerShow() {
|
||||
return cmd('profile show "Profile 1"', "", "profileSwitched").then(() => {
|
||||
is(gPanel.getProfileByName("Profile 1"), gPanel.activeProfile, "Profile 1 is active");
|
||||
return cmd('profile show "invalid"', gcli.lookup("profilerNotFound"));
|
||||
});
|
||||
}
|
||||
|
||||
function testProfilerClose() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
helpers.audit(gOptions, [{
|
||||
setup: "profiler close",
|
||||
exec: { output: "" }
|
||||
}]).then(function() {
|
||||
let toolbox = gDevTools.getToolbox(gOptions.target);
|
||||
if (!toolbox) {
|
||||
ok(true, "Profiler was closed.");
|
||||
deferred.resolve();
|
||||
} else {
|
||||
toolbox.on("destroyed", () => {
|
||||
ok(true, "Profiler was closed.");
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
function finishUp() {
|
||||
gTarget = null;
|
||||
gPanel = null;
|
||||
gOptions = null;
|
||||
finish();
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler is populated by console recordings that have finished
|
||||
* before it was opened.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let profilerConnected = waitForProfilerConnection();
|
||||
|
||||
let [target, debuggee, networkPanel] = yield initFrontend(SIMPLE_URL, "netmonitor");
|
||||
let toolbox = networkPanel._toolbox;
|
||||
|
||||
let SharedProfilerUtils = devtools.require("devtools/profiler/shared");
|
||||
let sharedProfilerConnection = SharedProfilerUtils.getProfilerConnection(toolbox);
|
||||
|
||||
yield profilerConnected;
|
||||
yield consoleProfile(sharedProfilerConnection, 1);
|
||||
yield consoleProfile(sharedProfilerConnection, 2);
|
||||
yield consoleProfileEnd(sharedProfilerConnection);
|
||||
yield consoleProfileEnd(sharedProfilerConnection);
|
||||
|
||||
yield toolbox.selectTool("jsprofiler");
|
||||
let profilerPanel = toolbox.getCurrentPanel();
|
||||
let { $, EVENTS, RecordingsListView, ProfileView } = profilerPanel.panelWin;
|
||||
|
||||
yield profilerPanel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
ok(true, "The first already finished console recording was automatically displayed.");
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should be two recordings visible.");
|
||||
ok(!$(".side-menu-widget-empty-text"),
|
||||
"There shouldn't be any empty text displayed in the recordings list.");
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should be one tab visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should be displayed in the profile view.");
|
||||
|
||||
is(RecordingsListView.items[0], RecordingsListView.selectedItem,
|
||||
"The first recording item should be automatically selected.");
|
||||
|
||||
is(RecordingsListView.items[0].attachment.profilerData.profileLabel, "2",
|
||||
"The profile label for the first recording is correct.");
|
||||
ok(!RecordingsListView.items[0].isRecording,
|
||||
"The 'isRecording' flag for the first recording is correct.");
|
||||
|
||||
is(RecordingsListView.items[1].attachment.profilerData.profileLabel, "1",
|
||||
"The profile label for the second recording is correct.");
|
||||
ok(!RecordingsListView.items[1].isRecording,
|
||||
"The 'isRecording' flag for the second recording is correct.");
|
||||
|
||||
yield teardown(networkPanel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(connection, label) {
|
||||
let notified = connection.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(connection) {
|
||||
let notified = connection.once("profileEnd");
|
||||
console.profileEnd();
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler is populated by in-progress console recordings
|
||||
* when it is opened.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let profilerConnected = waitForProfilerConnection();
|
||||
|
||||
let [target, debuggee, networkPanel] = yield initFrontend(SIMPLE_URL, "netmonitor");
|
||||
let toolbox = networkPanel._toolbox;
|
||||
|
||||
let SharedProfilerUtils = devtools.require("devtools/profiler/shared");
|
||||
let sharedProfilerConnection = SharedProfilerUtils.getProfilerConnection(toolbox);
|
||||
|
||||
yield profilerConnected;
|
||||
yield consoleProfile(sharedProfilerConnection, 1);
|
||||
yield consoleProfile(sharedProfilerConnection, 2);
|
||||
|
||||
yield toolbox.selectTool("jsprofiler");
|
||||
let profilerPanel = toolbox.getCurrentPanel();
|
||||
let { $, L10N, RecordingsListView, ProfileView } = profilerPanel.panelWin;
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should be two recordings visible.");
|
||||
ok(!$(".side-menu-widget-empty-text"),
|
||||
"There shouldn't be any empty text displayed in the recordings list.");
|
||||
|
||||
is(ProfileView.tabCount, 0,
|
||||
"There shouldn't be any tabs visible yet.");
|
||||
is($("#profile-pane").selectedPanel, $("#recording-notice"),
|
||||
"There should be a recording notice displayed in the profile view.");
|
||||
|
||||
is(RecordingsListView.items[0], RecordingsListView.selectedItem,
|
||||
"The first recording item should be automatically selected.");
|
||||
|
||||
is(RecordingsListView.items[0].attachment.profilerData.profileLabel, "1",
|
||||
"The profile label for the first recording is correct.");
|
||||
ok(RecordingsListView.items[0].isRecording,
|
||||
"The 'isRecording' flag for the first recording is correct.");
|
||||
|
||||
is(RecordingsListView.items[1].attachment.profilerData.profileLabel, "2",
|
||||
"The profile label for the second recording is correct.");
|
||||
ok(RecordingsListView.items[1].isRecording,
|
||||
"The 'isRecording' flag for the second recording is correct.");
|
||||
|
||||
let firstTarget = RecordingsListView.items[0].target;
|
||||
let secondTarget = RecordingsListView.items[1].target;
|
||||
|
||||
is($(".recording-item-title", firstTarget).getAttribute("value"), "1",
|
||||
"The first recording item's title is correct.");
|
||||
is($(".recording-item-title", secondTarget).getAttribute("value"), "2",
|
||||
"The second recording item's title is correct.");
|
||||
|
||||
is($(".recording-item-duration", firstTarget).getAttribute("value"),
|
||||
L10N.getStr("recordingsList.recordingLabel"),
|
||||
"The first recording item's duration is correct.");
|
||||
is($(".recording-item-duration", secondTarget).getAttribute("value"),
|
||||
L10N.getStr("recordingsList.recordingLabel"),
|
||||
"The second recording item's duration is correct.");
|
||||
|
||||
is($(".recording-item-save", firstTarget).getAttribute("value"), "",
|
||||
"The first recording item's save link should be invisible.");
|
||||
is($(".recording-item-save", secondTarget).getAttribute("value"), "",
|
||||
"The second recording item's save link should be invisible.");
|
||||
|
||||
yield teardown(profilerPanel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(connection, label) {
|
||||
let notified = connection.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler is populated by in-progress console recordings, and
|
||||
* also console recordings that have finished before it was opened.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let profilerConnected = waitForProfilerConnection();
|
||||
|
||||
let [target, debuggee, networkPanel] = yield initFrontend(SIMPLE_URL, "netmonitor");
|
||||
let toolbox = networkPanel._toolbox;
|
||||
|
||||
let SharedProfilerUtils = devtools.require("devtools/profiler/shared");
|
||||
let sharedProfilerConnection = SharedProfilerUtils.getProfilerConnection(toolbox);
|
||||
|
||||
yield profilerConnected;
|
||||
yield consoleProfile(sharedProfilerConnection, 1);
|
||||
yield consoleProfile(sharedProfilerConnection, 2);
|
||||
yield consoleProfileEnd(sharedProfilerConnection);
|
||||
|
||||
yield toolbox.selectTool("jsprofiler");
|
||||
let profilerPanel = toolbox.getCurrentPanel();
|
||||
let { $, EVENTS, RecordingsListView, ProfileView } = profilerPanel.panelWin;
|
||||
|
||||
yield profilerPanel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
ok(true, "The already finished console recording was automatically displayed.");
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should be two recordings visible.");
|
||||
ok(!$(".side-menu-widget-empty-text"),
|
||||
"There shouldn't be any empty text displayed in the recordings list.");
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should be one tab visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should be displayed in the profile view.");
|
||||
|
||||
is(RecordingsListView.items[0], RecordingsListView.selectedItem,
|
||||
"The first recording item should be automatically selected.");
|
||||
|
||||
is(RecordingsListView.items[0].attachment.profilerData.profileLabel, "2",
|
||||
"The profile label for the first recording is correct.");
|
||||
ok(!RecordingsListView.items[0].isRecording,
|
||||
"The 'isRecording' flag for the first recording is correct.");
|
||||
|
||||
is(RecordingsListView.items[1].attachment.profilerData.profileLabel, "1",
|
||||
"The profile label for the second recording is correct.");
|
||||
ok(RecordingsListView.items[1].isRecording,
|
||||
"The 'isRecording' flag for the second recording is correct.");
|
||||
|
||||
let recordingDisplayed = profilerPanel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
yield consoleProfileEnd(sharedProfilerConnection);
|
||||
yield recordingDisplayed;
|
||||
ok(true, "The newly finished console recording was automatically displayed.");
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should still be two recordings visible.");
|
||||
ok(!$(".side-menu-widget-empty-text"),
|
||||
"There still shouldn't be any empty text displayed in the recordings list.");
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should still be one tab visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should still be displayed in the profile view.");
|
||||
|
||||
is(RecordingsListView.items[1], RecordingsListView.selectedItem,
|
||||
"The second recording item should still be selected.");
|
||||
|
||||
is(RecordingsListView.items[0].attachment.profilerData.profileLabel, "2",
|
||||
"The profile label for the first recording is still correct.");
|
||||
ok(!RecordingsListView.items[0].isRecording,
|
||||
"The 'isRecording' flag for the first recording is still correct.");
|
||||
|
||||
is(RecordingsListView.items[1].attachment.profilerData.profileLabel, "1",
|
||||
"The profile label for the second recording is still correct.");
|
||||
ok(!RecordingsListView.items[1].isRecording,
|
||||
"The 'isRecording' flag for the second recording is still correct.");
|
||||
|
||||
yield teardown(profilerPanel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(connection, label) {
|
||||
let notified = connection.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(connection) {
|
||||
let notified = connection.once("profileEnd");
|
||||
console.profileEnd();
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler is correctly populated by new console recordings
|
||||
* after it is opened.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, gFront, RecordingsListView, ProfileView } = panel.panelWin;
|
||||
|
||||
yield consoleProfile(gFront, "hello world");
|
||||
|
||||
is(RecordingsListView.itemCount, 1,
|
||||
"There should be one recording visible.");
|
||||
ok(!$(".side-menu-widget-empty-text"),
|
||||
"There shouldn't be any empty text displayed in the recordings list.");
|
||||
|
||||
is(ProfileView.tabCount, 0,
|
||||
"There shouldn't be any tabs visible yet.");
|
||||
is($("#profile-pane").selectedPanel, $("#recording-notice"),
|
||||
"There should be a recording notice displayed in the profile view.");
|
||||
|
||||
is(RecordingsListView.items[0], RecordingsListView.selectedItem,
|
||||
"The first and only recording item should be selected.");
|
||||
|
||||
is(RecordingsListView.items[0].attachment.profilerData.profileLabel, "hello world",
|
||||
"The profile label for the first recording is correct.");
|
||||
ok(RecordingsListView.items[0].isRecording,
|
||||
"The 'isRecording' flag for the first recording is correct.");
|
||||
|
||||
let recordingDisplayed = panel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
yield consoleProfileEnd(gFront);
|
||||
yield recordingDisplayed;
|
||||
ok(true, "The newly finished console recording was automatically displayed.");
|
||||
|
||||
is(RecordingsListView.itemCount, 1,
|
||||
"There should still be one recording visible.");
|
||||
ok(!$(".side-menu-widget-empty-text"),
|
||||
"There still shouldn't be any empty text displayed in the recordings list.");
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should still be one tab visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should still be displayed in the profile view.");
|
||||
|
||||
is(RecordingsListView.items[0], RecordingsListView.selectedItem,
|
||||
"The first and only recording item should still be selected.");
|
||||
|
||||
is(RecordingsListView.items[0].attachment.profilerData.profileLabel, "hello world",
|
||||
"The profile label for the first recording is still correct.");
|
||||
ok(!RecordingsListView.items[0].isRecording,
|
||||
"The 'isRecording' flag for the first recording is still correct.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(front, label) {
|
||||
let notified = front.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(front) {
|
||||
let notified = front.once("profileEnd");
|
||||
console.profileEnd();
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler is correctly populated by sequential console recordings
|
||||
* with the same label, after it is opened.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, gFront, RecordingsListView, ProfileView } = panel.panelWin;
|
||||
|
||||
yield consoleProfile(gFront, "hello world");
|
||||
let firstRecordingDisplayed = panel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
yield consoleProfileEnd(gFront);
|
||||
yield firstRecordingDisplayed;
|
||||
ok(true, "The newly finished console recording was automatically displayed.");
|
||||
|
||||
yield consoleProfile(gFront, "hello world");
|
||||
let secondRecordingDisplayed = panel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
yield consoleProfileEnd(gFront);
|
||||
yield secondRecordingDisplayed;
|
||||
ok(true, "The newly finished console recording was automatically redisplayed.");
|
||||
|
||||
is(RecordingsListView.itemCount, 1,
|
||||
"There should be just one recording visible.");
|
||||
ok(!$(".side-menu-widget-empty-text"),
|
||||
"There shouldn't be any empty text displayed in the recordings list.");
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should be one tab visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should be displayed in the profile view.");
|
||||
|
||||
is(RecordingsListView.items[0], RecordingsListView.selectedItem,
|
||||
"The first and only recording item should be selected.");
|
||||
|
||||
is(RecordingsListView.items[0].attachment.profilerData.profileLabel, "hello world",
|
||||
"The profile label for the first recording is correct.");
|
||||
ok(!RecordingsListView.items[0].isRecording,
|
||||
"The 'isRecording' flag for the first recording is correct.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(front, label) {
|
||||
let notified = front.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(front) {
|
||||
let notified = front.once("profileEnd");
|
||||
console.profileEnd();
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler can correctly handle sequential console recordings,
|
||||
* finished in reverse order.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, gFront, RecordingsListView, ProfileView } = panel.panelWin;
|
||||
|
||||
yield consoleProfile(gFront, "1");
|
||||
yield consoleProfile(gFront, "2");
|
||||
|
||||
let firstRecordingDisplayed = panel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
yield consoleProfileEnd(gFront, "1");
|
||||
yield firstRecordingDisplayed;
|
||||
ok(true, "The newly finished console recording was automatically displayed.");
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should be two recordings visible.");
|
||||
is(RecordingsListView.items[0], RecordingsListView.selectedItem,
|
||||
"The first recording item should be selected.");
|
||||
|
||||
is(RecordingsListView.items[0].attachment.profilerData.profileLabel, "1",
|
||||
"The profile label for the first recording is correct.");
|
||||
ok(!RecordingsListView.items[0].isRecording,
|
||||
"The 'isRecording' flag for the first recording is correct.");
|
||||
|
||||
is(RecordingsListView.items[1].attachment.profilerData.profileLabel, "2",
|
||||
"The profile label for the second recording is correct.");
|
||||
ok(RecordingsListView.items[1].isRecording,
|
||||
"The 'isRecording' flag for the second recording is correct.");
|
||||
|
||||
let secondRecordingDisplayed = panel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
yield consoleProfileEnd(gFront, "2");
|
||||
yield secondRecordingDisplayed;
|
||||
ok(true, "The newly finished console recording was automatically redisplayed.");
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should still be two recordings visible.");
|
||||
is(RecordingsListView.items[1], RecordingsListView.selectedItem,
|
||||
"The second recording item should now be selected.");
|
||||
|
||||
is(RecordingsListView.items[0].attachment.profilerData.profileLabel, "1",
|
||||
"The profile label for the first recording is still correct.");
|
||||
ok(!RecordingsListView.items[0].isRecording,
|
||||
"The 'isRecording' flag for the first recording is still correct.");
|
||||
|
||||
is(RecordingsListView.items[1].attachment.profilerData.profileLabel, "2",
|
||||
"The profile label for the second recording is still correct.");
|
||||
ok(!RecordingsListView.items[1].isRecording,
|
||||
"The 'isRecording' flag for the second recording is still correct.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(front, label) {
|
||||
let notified = front.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(front, label) {
|
||||
let notified = front.once("profileEnd");
|
||||
console.profileEnd(label);
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler can correctly handle sequential console recordings,
|
||||
* finished in reverse order, and the second call to `console.profileEnd`
|
||||
* doesn't have any argument.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, gFront, RecordingsListView, ProfileView } = panel.panelWin;
|
||||
|
||||
yield consoleProfile(gFront, "1");
|
||||
yield consoleProfile(gFront, "2");
|
||||
|
||||
let firstRecordingDisplayed = panel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
yield consoleProfileEnd(gFront, true, "1");
|
||||
yield firstRecordingDisplayed;
|
||||
ok(true, "The newly finished console recording was automatically displayed.");
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should be two recordings visible.");
|
||||
is(RecordingsListView.items[0], RecordingsListView.selectedItem,
|
||||
"The first recording item should be selected.");
|
||||
|
||||
is(RecordingsListView.items[0].attachment.profilerData.profileLabel, "1",
|
||||
"The profile label for the first recording is correct.");
|
||||
ok(!RecordingsListView.items[0].isRecording,
|
||||
"The 'isRecording' flag for the first recording is correct.");
|
||||
|
||||
is(RecordingsListView.items[1].attachment.profilerData.profileLabel, "2",
|
||||
"The profile label for the second recording is correct.");
|
||||
ok(RecordingsListView.items[1].isRecording,
|
||||
"The 'isRecording' flag for the second recording is correct.");
|
||||
|
||||
let secondRecordingDisplayed = panel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
yield consoleProfileEnd(gFront);
|
||||
yield secondRecordingDisplayed;
|
||||
ok(true, "The newly finished console recording was automatically redisplayed.");
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should still be two recordings visible.");
|
||||
is(RecordingsListView.items[1], RecordingsListView.selectedItem,
|
||||
"The second recording item should now be selected.");
|
||||
|
||||
is(RecordingsListView.items[0].attachment.profilerData.profileLabel, "1",
|
||||
"The profile label for the first recording is still correct.");
|
||||
ok(!RecordingsListView.items[0].isRecording,
|
||||
"The 'isRecording' flag for the first recording is still correct.");
|
||||
|
||||
is(RecordingsListView.items[1].attachment.profilerData.profileLabel, "2",
|
||||
"The profile label for the second recording is still correct.");
|
||||
ok(!RecordingsListView.items[1].isRecording,
|
||||
"The 'isRecording' flag for the second recording is still correct.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(front, label) {
|
||||
let notified = front.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(front, withLabel, labelValue) {
|
||||
let notified = front.once("profileEnd");
|
||||
if (!withLabel) {
|
||||
console.profileEnd();
|
||||
} else {
|
||||
console.profileEnd(labelValue);
|
||||
}
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler can correctly handle sequential console recordings,
|
||||
* finished in reverse order, and the second call to `console.profileEnd`
|
||||
* contains the same label as the first call.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, gFront, RecordingsListView, ProfileView } = panel.panelWin;
|
||||
|
||||
yield consoleProfile(gFront, "1");
|
||||
yield consoleProfile(gFront, "2");
|
||||
|
||||
let firstRecordingDisplayed = panel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
yield consoleProfileEnd(gFront, "1");
|
||||
yield firstRecordingDisplayed;
|
||||
ok(true, "The newly finished console recording was automatically displayed.");
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should be two recordings visible.");
|
||||
is(RecordingsListView.items[0], RecordingsListView.selectedItem,
|
||||
"The first recording item should be selected.");
|
||||
|
||||
is(RecordingsListView.items[0].attachment.profilerData.profileLabel, "1",
|
||||
"The profile label for the first recording is correct.");
|
||||
ok(!RecordingsListView.items[0].isRecording,
|
||||
"The 'isRecording' flag for the first recording is correct.");
|
||||
|
||||
is(RecordingsListView.items[1].attachment.profilerData.profileLabel, "2",
|
||||
"The profile label for the second recording is correct.");
|
||||
ok(RecordingsListView.items[1].isRecording,
|
||||
"The 'isRecording' flag for the second recording is correct.");
|
||||
|
||||
gFront.once("profileEnd", () => {
|
||||
ok(false, "The second console recording shouldn't have ended.")
|
||||
});
|
||||
panel.panelWin.once(EVENTS.RECORDING_DISPLAYED, () => {
|
||||
ok(false, "The second console recording shouldn't have been displayed.");
|
||||
});
|
||||
|
||||
console.profileEnd("1");
|
||||
yield DevToolsUtils.waitForTime(1000);
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(front, label) {
|
||||
let notified = front.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(front, label) {
|
||||
let notified = front.once("profileEnd");
|
||||
console.profileEnd(label);
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler can correctly handle simultaneous console and manual
|
||||
* recordings (via `console.profile` and clicking the record button).
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, gFront, RecordingsListView, ProfileView } = panel.panelWin;
|
||||
|
||||
info("Starting a manual recording...");
|
||||
yield startRecording(panel);
|
||||
|
||||
info("Starting two console recordings, one without a label, one with.");
|
||||
yield consoleProfile(gFront);
|
||||
yield consoleProfile(gFront, true, "hello world");
|
||||
|
||||
is(RecordingsListView.itemCount, 3,
|
||||
"There should be three recordings visible now.");
|
||||
is(RecordingsListView.selectedIndex, 0,
|
||||
"The first recording item was selected in the list.");
|
||||
|
||||
testListItem(RecordingsListView, 0, undefined, true);
|
||||
testListItem(RecordingsListView, 1, undefined, true);
|
||||
testListItem(RecordingsListView, 2, "hello world", true);
|
||||
|
||||
info("Stopping the manual recording...");
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
is(RecordingsListView.itemCount, 3,
|
||||
"There should still be three recordings visible now.");
|
||||
is(RecordingsListView.selectedIndex, 0,
|
||||
"The first recording item is still selected in the list.");
|
||||
|
||||
testListItem(RecordingsListView, 0, undefined, false);
|
||||
testListItem(RecordingsListView, 1, undefined, true);
|
||||
testListItem(RecordingsListView, 2, "hello world", true);
|
||||
|
||||
info("Stopping the unlabeled console recording...");
|
||||
yield consoleProfileEnd(gFront);
|
||||
|
||||
is(RecordingsListView.itemCount, 3,
|
||||
"There should still be three recordings visible now.");
|
||||
is(RecordingsListView.selectedIndex, 1,
|
||||
"The second recording item was selected in the list.");
|
||||
|
||||
testListItem(RecordingsListView, 0, undefined, false);
|
||||
testListItem(RecordingsListView, 1, undefined, false);
|
||||
testListItem(RecordingsListView, 2, "hello world", true);
|
||||
|
||||
info("Stopping the labeled console recording...");
|
||||
yield consoleProfileEnd(gFront);
|
||||
|
||||
is(RecordingsListView.itemCount, 3,
|
||||
"There should still be three recordings visible now.");
|
||||
is(RecordingsListView.selectedIndex, 2,
|
||||
"The third recording item was selected in the list.");
|
||||
|
||||
testListItem(RecordingsListView, 0, undefined, false);
|
||||
testListItem(RecordingsListView, 1, undefined, false);
|
||||
testListItem(RecordingsListView, 2, "hello world", false);
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function testListItem(view, index, profileLabel, isRecording) {
|
||||
is(view.items[index].attachment.profilerData.profileLabel, profileLabel,
|
||||
"The recording item at index " + index + " has a correct profile label.");
|
||||
is(!!view.items[index].isRecording, isRecording,
|
||||
"The recording item at index " + index + " has a correct `isRecording` flag.");
|
||||
}
|
||||
|
||||
function* consoleProfile(front, withLabel, labelValue) {
|
||||
let notified = front.once("profile");
|
||||
if (!withLabel) {
|
||||
console.profile();
|
||||
} else {
|
||||
console.profile(labelValue);
|
||||
}
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(front) {
|
||||
let notified = front.once("profileEnd");
|
||||
console.profileEnd();
|
||||
yield notified;
|
||||
}
|
@ -1,35 +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, (tab, browser, panel) => {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
openConsole(tab, testConsoleProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function testConsoleProfile(hud) {
|
||||
hud.jsterm.clearOutput(true);
|
||||
|
||||
let profilesStarted = 0;
|
||||
|
||||
gPanel.once("parsed", () => {
|
||||
let profile = gPanel.activeProfile;
|
||||
|
||||
is(profile.name, "Profile 1", "Profile name is OK");
|
||||
is(gPanel.sidebar.selectedItem, gPanel.sidebar.getItemByProfile(profile), "Sidebar is OK");
|
||||
is(gPanel.sidebar.selectedItem.attachment.state, PROFILE_COMPLETED);
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
|
||||
hud.jsterm.execute("console.profile()");
|
||||
hud.jsterm.execute("console.profileEnd()");
|
||||
}
|
@ -1,55 +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>";
|
||||
const BASE = "http://example.com/browser/browser/devtools/profiler/test/";
|
||||
const PAGE = BASE + "mock_console_api.html";
|
||||
|
||||
let gTab, gPanel, gToolbox;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, (tab, browser, panel) => {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
openProfiler(tab, (toolbox) => {
|
||||
gToolbox = toolbox;
|
||||
loadUrl(PAGE, tab, () => {
|
||||
gPanel.sidebar.on("stateChanged", (_, item) => {
|
||||
if (item.attachment.state !== PROFILE_COMPLETED)
|
||||
return;
|
||||
|
||||
runTests();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
is(getSidebarItem(1).attachment.state, PROFILE_COMPLETED);
|
||||
|
||||
gPanel.once("parsed", () => {
|
||||
function assertSampleAndFinish() {
|
||||
let [win,doc] = getProfileInternals();
|
||||
let sample = doc.getElementsByClassName("samplePercentage");
|
||||
|
||||
if (sample.length <= 0)
|
||||
return void setTimeout(assertSampleAndFinish, 100);
|
||||
|
||||
ok(sample.length > 0, "We have Cleopatra UI displayed");
|
||||
tearDown(gTab, () => {
|
||||
gTab = null;
|
||||
gPanel = null;
|
||||
gToolbox = null;
|
||||
});
|
||||
}
|
||||
|
||||
assertSampleAndFinish();
|
||||
});
|
||||
|
||||
let profile = gPanel.profiles.get(1);
|
||||
gPanel.sidebar.selectedItem = gPanel.sidebar.getItemByProfile(profile);
|
||||
}
|
@ -1,37 +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, (tab, browser, panel) => {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
openProfiler(tab, runTests);
|
||||
});
|
||||
}
|
||||
|
||||
function runTests(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
let record = gPanel.controls.record;
|
||||
|
||||
panel.once("started", () => {
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_RUNNING);
|
||||
|
||||
openConsole(gTab, (hud) => {
|
||||
panel.once("stopped", () => {
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_COMPLETED);
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
|
||||
hud.jsterm.execute("console.profileEnd()");
|
||||
});
|
||||
});
|
||||
|
||||
record.click();
|
||||
}
|
@ -1,56 +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, (tab, browser, panel) => {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
openConsole(tab, testConsoleProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function testConsoleProfile(hud) {
|
||||
hud.jsterm.clearOutput(true);
|
||||
|
||||
// Here we start two named profiles and then end one of them.
|
||||
|
||||
let profilesStarted = 0;
|
||||
|
||||
function endProfile() {
|
||||
if (++profilesStarted < 2)
|
||||
return;
|
||||
|
||||
gPanel.controller.off("profileStart", endProfile);
|
||||
gPanel.controller.once("profileEnd", () => openProfiler(gTab, checkProfiles));
|
||||
hud.jsterm.execute("console.profileEnd('Second')");
|
||||
}
|
||||
|
||||
gPanel.controller.on("profileStart", endProfile);
|
||||
hud.jsterm.execute("console.profile('Second')");
|
||||
hud.jsterm.execute("console.profile('Third')");
|
||||
}
|
||||
|
||||
function checkProfiles(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
|
||||
is(getSidebarItem(1, panel).attachment.name, "Second", "Name in sidebar is OK");
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_COMPLETED, "State in sidebar is OK");
|
||||
|
||||
// Make sure we can still stop profiles via the queue pop.
|
||||
|
||||
gPanel.controller.once("profileEnd", () => {
|
||||
openProfiler(gTab, () => {
|
||||
is(getSidebarItem(2, panel).attachment.state, PROFILE_COMPLETED, "State in sidebar is OK");
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
});
|
||||
|
||||
openConsole(gTab, (hud) => hud.jsterm.execute("console.profileEnd()"));
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests the function testing whether or not a frame is content or chrome
|
||||
* works properly.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
let { _isContent } = devtools.require("devtools/profiler/tree-model");
|
||||
|
||||
ok(_isContent({ location: "http://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(_isContent({ location: "https://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(_isContent({ location: "file://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
|
||||
ok(!_isContent({ location: "chrome://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!_isContent({ location: "resource://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
|
||||
ok(!_isContent({ location: "chrome://foo -> http://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!_isContent({ location: "chrome://foo -> https://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!_isContent({ location: "chrome://foo -> file://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
|
||||
ok(!_isContent({ location: "resource://foo -> http://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!_isContent({ location: "resource://foo -> https://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!_isContent({ location: "resource://foo -> file://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
|
||||
ok(!_isContent({ category: 1, location: "chrome://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!_isContent({ category: 1, location: "resource://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
|
||||
ok(!_isContent({ category: 1, location: "file://foo -> http://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!_isContent({ category: 1, location: "file://foo -> https://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!_isContent({ category: 1, location: "file://foo -> file://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
|
||||
finish();
|
||||
}
|
@ -1,64 +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;
|
||||
|
||||
testInactive(startFirstProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function testInactive(next=function(){}) {
|
||||
gPanel.controller.isActive(function (err, isActive) {
|
||||
ok(!err, "isActive didn't return any errors");
|
||||
ok(!isActive, "Profiler is not active");
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
function testActive(next=function(){}) {
|
||||
gPanel.controller.isActive(function (err, isActive) {
|
||||
ok(!err, "isActive didn't return any errors");
|
||||
ok(isActive, "Profiler is active");
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
function startFirstProfile() {
|
||||
gPanel.controller.start("Profile 1", function (err) {
|
||||
ok(!err, "Profile 1 started without errors");
|
||||
testActive(startSecondProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function startSecondProfile() {
|
||||
gPanel.controller.start("Profile 2", function (err) {
|
||||
ok(!err, "Profile 2 started without errors");
|
||||
testActive(stopFirstProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function stopFirstProfile() {
|
||||
gPanel.controller.stop("Profile 1", function (err, data) {
|
||||
ok(!err, "Profile 1 stopped without errors");
|
||||
ok(data, "Profiler returned some data");
|
||||
|
||||
testActive(stopSecondProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function stopSecondProfile() {
|
||||
gPanel.controller.stop("Profile 2", function (err, data) {
|
||||
ok(!err, "Profile 2 stopped without errors");
|
||||
ok(data, "Profiler returned some data");
|
||||
testInactive(tearDown.call(null, gTab, function () gTab = gPanel = null));
|
||||
});
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the retrieved profiler data samples are correctly filtered and
|
||||
* normalized before passed to consumers.
|
||||
*/
|
||||
|
||||
const WAIT_TIME = 1000; // ms
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
// Perform the first recording...
|
||||
|
||||
yield front.startRecording();
|
||||
let profilingStartTime = front._profilingStartTime;
|
||||
info("Started profiling at: " + profilingStartTime);
|
||||
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
|
||||
|
||||
let firstRecordingData = yield front.stopRecording();
|
||||
let firstRecordingFinishTime = firstRecordingData.profilerData.currentTime;
|
||||
|
||||
is(profilingStartTime, 0,
|
||||
"The profiling start time should be 0 for the first recording.");
|
||||
ok(firstRecordingData.recordingDuration >= WAIT_TIME,
|
||||
"The first recording duration is correct.");
|
||||
ok(firstRecordingFinishTime >= WAIT_TIME,
|
||||
"The first recording finish time is correct.");
|
||||
|
||||
// Perform the second recording...
|
||||
|
||||
yield front.startRecording();
|
||||
let profilingStartTime = front._profilingStartTime;
|
||||
info("Started profiling at: " + profilingStartTime);
|
||||
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample more cpu activity
|
||||
|
||||
let secondRecordingData = yield front.stopRecording();
|
||||
let secondRecordingFinishTime = secondRecordingData.profilerData.currentTime;
|
||||
let secondRecordingProfile = secondRecordingData.profilerData.profile;
|
||||
let secondRecordingSamples = secondRecordingProfile.threads[0].samples;
|
||||
|
||||
isnot(profilingStartTime, 0,
|
||||
"The profiling start time should not be 0 on the second recording.");
|
||||
ok(secondRecordingData.recordingDuration >= WAIT_TIME,
|
||||
"The second recording duration is correct.");
|
||||
ok(secondRecordingFinishTime - firstRecordingFinishTime >= WAIT_TIME,
|
||||
"The second recording finish time is correct.");
|
||||
|
||||
ok(secondRecordingSamples[0].time < profilingStartTime,
|
||||
"The second recorded sample times were normalized.");
|
||||
ok(secondRecordingSamples[0].time > 0,
|
||||
"The second recorded sample times were normalized correctly.");
|
||||
ok(!secondRecordingSamples.find(e => e.time + profilingStartTime <= firstRecordingFinishTime),
|
||||
"There should be no samples from the first recording in the second one, " +
|
||||
"even though the total number of frames did not overflow.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,52 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the retrieved profiler data samples are correctly filtered and
|
||||
* normalized before passed to consumers.
|
||||
*/
|
||||
|
||||
const WAIT_TIME = 1000; // ms
|
||||
const FRAMES_OVERFLOW = 1000;
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
front._customProfilerOptions = { entries: FRAMES_OVERFLOW };
|
||||
|
||||
yield front.startRecording();
|
||||
let profilingStartTime = front._profilingStartTime;
|
||||
info("Started profiling at: " + profilingStartTime);
|
||||
|
||||
yield idleWait(WAIT_TIME); // allow refresh driver ticks to accumulate
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
|
||||
yield idleWait(WAIT_TIME); // allow refresh driver ticks to accumulate more
|
||||
|
||||
let recordingData = yield front.stopRecording();
|
||||
let ticks = recordingData.ticksData;
|
||||
let profile = recordingData.profilerData.profile;
|
||||
let samples = profile.threads[0].samples;
|
||||
|
||||
ok(samples[0].time >= WAIT_TIME,
|
||||
"The recorded samples overflowed, so the older ones were clamped.");
|
||||
|
||||
if (ticks.length) {
|
||||
ok(ticks[0] >= samples[0].time,
|
||||
"The refresh driver ticks were filtered before being retrieved (1).");
|
||||
ok(ticks[ticks.length - 1] <= findOldestSampleTime(samples),
|
||||
"The refresh driver ticks were filtered before being retrieved (2).");
|
||||
}
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function findOldestSampleTime(samples) {
|
||||
for (let i = samples.length - 1; i >= 0; i--) {
|
||||
let sample = samples[i];
|
||||
if ("time" in sample) {
|
||||
return sample.time;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the retrieved profiler data samples always have a (root) node.
|
||||
* If this ever changes, the |ThreadNode.prototype.insert| function in
|
||||
* browser/devtools/profiler/utils/tree-model.js will have to be changed.
|
||||
*/
|
||||
|
||||
const WAIT_TIME = 1000; // ms
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
yield front.startRecording();
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
|
||||
|
||||
let recordingData = yield front.stopRecording();
|
||||
let profile = recordingData.profilerData.profile;
|
||||
|
||||
for (let thread of profile.threads) {
|
||||
info("Checking thread: " + thread.name);
|
||||
|
||||
for (let sample of thread.samples) {
|
||||
if (sample.frames[0].location != "(root)") {
|
||||
ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -1,43 +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 (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();
|
||||
});
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profile view is rebuilt every time the
|
||||
* devtools.profiler.ui.show-platform-data pref is changed.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, Prefs, RecordingsListView, ProfileView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
is(RecordingsListView.itemCount, 1,
|
||||
"There should be one recording visible.");
|
||||
ok(!$(".side-menu-widget-empty-text"),
|
||||
"There shouldn't be any empty text displayed in the recordings list.");
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should be one tab visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should be displayed in the profile view.");
|
||||
|
||||
let emptyNoticeDisplayed = panel.panelWin.once(EVENTS.EMPTY_NOTICE_SHOWN);
|
||||
let tabbedBrowserRedisplayed = panel.panelWin.once(EVENTS.TABBED_BROWSER_SHOWN);
|
||||
let recordingRedisplayed = panel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
|
||||
let prevPrefValue = Prefs.showPlatformData;
|
||||
Prefs.showPlatformData ^= 1;
|
||||
|
||||
yield emptyNoticeDisplayed;
|
||||
yield tabbedBrowserRedisplayed;
|
||||
yield recordingRedisplayed;
|
||||
ok(true, "The profile view was rebuilt.");
|
||||
|
||||
is(RecordingsListView.itemCount, 1,
|
||||
"There should still be one recording visible.");
|
||||
ok(!$(".side-menu-widget-empty-text"),
|
||||
"There still shouldn't be any empty text displayed in the recordings list.");
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should still be one tab visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should still be displayed in the profile view.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -1,52 +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;
|
||||
|
||||
function done() {
|
||||
tearDown(gTab, () => { gPanel = null; gTab = null; });
|
||||
}
|
||||
|
||||
Services.prefs.setBoolPref(SHOW_PLATFORM_DATA, false);
|
||||
recordProfile()
|
||||
.then(toggleGeckoDataOption)
|
||||
.then(recordProfile)
|
||||
.then(done);
|
||||
});
|
||||
}
|
||||
|
||||
function recordProfile() {
|
||||
let deferred = promise.defer();
|
||||
let record = gPanel.controls.record;
|
||||
|
||||
gPanel.once("started", () => {
|
||||
gPanel.once("parsed", () => {
|
||||
// We cannot be sure which data is returned by
|
||||
// the profiler within a test. Until we get rid
|
||||
// of Cleopatra, at least.
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
record.click();
|
||||
});
|
||||
|
||||
record.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function toggleGeckoDataOption() {
|
||||
ok(!gPanel.showPlatformData, "showPlatformData is not set");
|
||||
|
||||
Services.prefs.setBoolPref(SHOW_PLATFORM_DATA, true);
|
||||
|
||||
ok(gPanel.showPlatformData, "showPlatformData is set");
|
||||
}
|
@ -1,80 +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>browser_profiler_io</p>";
|
||||
|
||||
let temp = {};
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm", temp);
|
||||
let FileUtils = temp.FileUtils;
|
||||
let gTab, gPanel;
|
||||
|
||||
let gData = {
|
||||
"libs": "[]", // This property is not important for this test.
|
||||
"meta": {
|
||||
"version": 2,
|
||||
"interval": 1,
|
||||
"stackwalk": 0,
|
||||
"jank": 0,
|
||||
"processType": 0,
|
||||
"platform": "Macintosh",
|
||||
"oscpu": "Intel Mac OS X 10.8",
|
||||
"misc": "rv:25.0",
|
||||
"abi": "x86_64-gcc3",
|
||||
"toolkit": "cocoa",
|
||||
"product": "Firefox"
|
||||
},
|
||||
"threads": [
|
||||
{
|
||||
"samples": [
|
||||
{
|
||||
"name": "(root)",
|
||||
"frames": [
|
||||
{
|
||||
"location": "Startup::XRE_Main",
|
||||
"line": 3871
|
||||
},
|
||||
{
|
||||
"location": "Events::ProcessGeckoEvents",
|
||||
"line": 355
|
||||
},
|
||||
{
|
||||
"location": "Events::ProcessGeckoEvents",
|
||||
"line": 355
|
||||
}
|
||||
],
|
||||
"responsiveness": -0.002963,
|
||||
"time": 8.120823
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function onSetUp(tab, browser, panel) {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
|
||||
|
||||
gPanel.saveProfile(file, gData)
|
||||
.then(gPanel.loadProfile.bind(gPanel, file))
|
||||
.then(checkData);
|
||||
});
|
||||
}
|
||||
|
||||
function checkData() {
|
||||
let profile = gPanel.activeProfile;
|
||||
let item = gPanel.sidebar.getItemByProfile(profile);
|
||||
let data = profile.data;
|
||||
|
||||
is(item.attachment.state, PROFILE_COMPLETED, "Profile is COMPLETED");
|
||||
is(gData.meta.oscpu, data.meta.oscpu, "Meta data is correct");
|
||||
is(gData.threads[0].samples.length, 1, "There's one sample");
|
||||
is(gData.threads[0].samples[0].name, "(root)", "Sample is correct");
|
||||
|
||||
tearDown(gTab, () => { gPanel = null; gTab = null; });
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler can jump to the debugger.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let toolbox = panel._toolbox;
|
||||
let EVENTS = panel.panelWin.EVENTS;
|
||||
|
||||
let whenSourceShown = panel.panelWin.once(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
|
||||
panel.panelWin.viewSourceInDebugger(SIMPLE_URL, 14);
|
||||
yield whenSourceShown;
|
||||
|
||||
let debuggerPanel = toolbox.getPanel("jsdebugger");
|
||||
ok(debuggerPanel, "The debugger panel was opened.");
|
||||
|
||||
let { DebuggerView } = debuggerPanel.panelWin;
|
||||
|
||||
is(DebuggerView.Sources.selectedValue, SIMPLE_URL,
|
||||
"The correct source is shown in the debugger.");
|
||||
is(DebuggerView.editor.getCursor().line + 1, 14,
|
||||
"The correct line is highlighted in the debugger's source editor.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,43 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler can jump to the debugger, when the source was already
|
||||
* loaded in that tool.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, debuggerPanel] = yield initFrontend(SIMPLE_URL, "jsdebugger");
|
||||
let toolbox = debuggerPanel._toolbox;
|
||||
let debuggerWin = debuggerPanel.panelWin;
|
||||
let debuggerEvents = debuggerWin.EVENTS;
|
||||
let { DebuggerView } = debuggerWin;
|
||||
|
||||
yield debuggerWin.once(debuggerEvents.SOURCE_SHOWN);
|
||||
ok("A source was shown in the debugger.");
|
||||
|
||||
is(DebuggerView.Sources.selectedValue, SIMPLE_URL,
|
||||
"The correct source is initially shown in the debugger.");
|
||||
is(DebuggerView.editor.getCursor().line, 0,
|
||||
"The correct line is initially highlighted in the debugger's source editor.");
|
||||
|
||||
yield toolbox.selectTool("jsprofiler");
|
||||
let profilerPanel = toolbox.getCurrentPanel();
|
||||
let profilerWin = profilerPanel.panelWin;
|
||||
let profilerEvents = profilerWin.EVENTS;
|
||||
|
||||
let whenSourceShown = profilerWin.once(profilerEvents.SOURCE_SHOWN_IN_JS_DEBUGGER);
|
||||
profilerWin.viewSourceInDebugger(SIMPLE_URL, 14);
|
||||
yield whenSourceShown;
|
||||
|
||||
let debuggerPanel = toolbox.getPanel("jsdebugger");
|
||||
ok(debuggerPanel, "The debugger panel was reselected.");
|
||||
|
||||
is(DebuggerView.Sources.selectedValue, SIMPLE_URL,
|
||||
"The correct source is still shown in the debugger.");
|
||||
is(DebuggerView.editor.getCursor().line + 1, 14,
|
||||
"The correct line is now highlighted in the debugger's source editor.");
|
||||
|
||||
yield teardown(profilerPanel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,35 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler shows the appropriate notice when a selection
|
||||
* from the recordings list is lost.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, RecordingsListView } = panel.panelWin;
|
||||
|
||||
is(RecordingsListView.itemCount, 0,
|
||||
"There should be no recordings visible yet.");
|
||||
is($("#profile-pane").selectedPanel, $("#empty-notice"),
|
||||
"There should be an empty notice displayed in the profile view.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
is(RecordingsListView.itemCount, 1,
|
||||
"There should be one recording visible now.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should be displayed in the profile view.");
|
||||
|
||||
RecordingsListView.selectedItem = null;
|
||||
|
||||
is(RecordingsListView.itemCount, 1,
|
||||
"There should still be one recording visible now.");
|
||||
is($("#profile-pane").selectedPanel, $("#empty-notice"),
|
||||
"There should be an empty notice displayed in the profile view again.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,46 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profile view correctly displays its panels and emits
|
||||
* the appropriate events.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, ProfileView } = panel.panelWin;
|
||||
|
||||
is($("#profile-pane").selectedPanel, $("#empty-notice"),
|
||||
"The empty notice should initially be displayed in the profile view.");
|
||||
|
||||
let recordingNoticeDisplayed = panel.panelWin.once(EVENTS.RECORDING_NOTICE_SHOWN);
|
||||
ProfileView.showRecordingNotice();
|
||||
yield recordingNoticeDisplayed;
|
||||
|
||||
is($("#profile-pane").selectedPanel, $("#recording-notice"),
|
||||
"The recording notice should now be displayed in the profile view.");
|
||||
|
||||
let loadingNoticeDisplayed = panel.panelWin.once(EVENTS.LOADING_NOTICE_SHOWN);
|
||||
ProfileView.showLoadingNotice();
|
||||
yield loadingNoticeDisplayed;
|
||||
|
||||
is($("#profile-pane").selectedPanel, $("#loading-notice"),
|
||||
"The loading notice should now be displayed in the profile view.");
|
||||
|
||||
let tabbedBrowserDisplayed = panel.panelWin.once(EVENTS.TABBED_BROWSER_SHOWN);
|
||||
ProfileView.showTabbedBrowser();
|
||||
yield tabbedBrowserDisplayed;
|
||||
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should now be displayed in the profile view.");
|
||||
|
||||
let emptyNoticeDisplayed = panel.panelWin.once(EVENTS.EMPTY_NOTICE_SHOWN);
|
||||
ProfileView.showEmptyNotice();
|
||||
yield emptyNoticeDisplayed;
|
||||
|
||||
is($("#profile-pane").selectedPanel, $("#empty-notice"),
|
||||
"The empty notice should now be redisplayed in the profile view.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,52 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler properly handles the built-in profiler module
|
||||
* suddenly stopping.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, gFront, EVENTS, RecordingsListView } = panel.panelWin;
|
||||
|
||||
yield consoleProfile(gFront, "test");
|
||||
yield startRecording(panel);
|
||||
|
||||
is(gFront.pendingConsoleRecordings.length, 1,
|
||||
"There should be one pending console recording.");
|
||||
is(gFront.finishedConsoleRecordings.length, 0,
|
||||
"There should be no finished console recordings.");
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should be two recordings visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#recording-notice"),
|
||||
"There should be a recording notice displayed in the profile view.");
|
||||
|
||||
let whenFrontendNotified = gFront.once("profiler-unexpectedly-stopped");
|
||||
let whenRecordingLost = panel.panelWin.once(EVENTS.RECORDING_LOST);
|
||||
nsIProfilerModule.StopProfiler();
|
||||
|
||||
yield whenFrontendNotified;
|
||||
ok(true, "The frontend was notified about the profiler being stopped.");
|
||||
|
||||
yield whenRecordingLost;
|
||||
ok(true, "The frontend reacted to the profiler being stopped.");
|
||||
|
||||
is(gFront.pendingConsoleRecordings.length, 0,
|
||||
"There should be no pending console recordings.");
|
||||
is(gFront.finishedConsoleRecordings.length, 0,
|
||||
"There should still be no finished console recordings.");
|
||||
is(RecordingsListView.itemCount, 0,
|
||||
"There should be no recordings visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#empty-notice"),
|
||||
"There should be an empty notice displayed in the profile view.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(front, label) {
|
||||
let notified = front.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler correctly handles multiple recordings and can
|
||||
* successfully switch between them.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, RecordingsListView, ProfileView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should be two recordings visible.");
|
||||
is(RecordingsListView.selectedIndex, 1,
|
||||
"The second recording item should be selected.");
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should be one tab visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should be displayed in the profile view.");
|
||||
|
||||
let recordingDisplayed = panel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
RecordingsListView.selectedIndex = 0;
|
||||
yield recordingDisplayed;
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should still be two recordings visible.");
|
||||
is(RecordingsListView.selectedIndex, 0,
|
||||
"The first recording item should be selected.");
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should still be one tab visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should still be displayed in the profile view.");
|
||||
|
||||
let emptyNoticeDisplayed = panel.panelWin.once(EVENTS.EMPTY_NOTICE_SHOWN);
|
||||
RecordingsListView.selectedIndex = -1;
|
||||
yield emptyNoticeDisplayed;
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should still be two recordings visible.");
|
||||
is(RecordingsListView.selectedItem, null,
|
||||
"No recording item should be selected.");
|
||||
|
||||
is($("#profile-pane").selectedPanel, $("#empty-notice"),
|
||||
"The empty notice should still be displayed in the profile view.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,56 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler correctly handles multiple recordings and can
|
||||
* successfully switch between them, even when one of them is in progress.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, RecordingsListView, ProfileView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should be two recordings visible.");
|
||||
is(RecordingsListView.selectedIndex, 0,
|
||||
"The first recording item should be selected.");
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should be one tab visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should still be displayed in the profile view.");
|
||||
|
||||
let recordingNoticeDisplayed = panel.panelWin.once(EVENTS.RECORDING_NOTICE_SHOWN);
|
||||
RecordingsListView.selectedIndex = 1;
|
||||
yield recordingNoticeDisplayed;
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should still be two recordings visible.");
|
||||
is(RecordingsListView.selectedIndex, 1,
|
||||
"The second recording item should be selected now.");
|
||||
|
||||
is($("#profile-pane").selectedPanel, $("#recording-notice"),
|
||||
"The recording notice should be displayed in the profile view.");
|
||||
|
||||
let recordingDisplayed = panel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
RecordingsListView.selectedIndex = 0;
|
||||
yield recordingDisplayed;
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should still be two recordings visible.");
|
||||
is(RecordingsListView.selectedIndex, 0,
|
||||
"The first recording item should be selected again.");
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should be one tab visible again.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should be displayed in the profile view again.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,68 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the recording utility functions work as expected.
|
||||
*/
|
||||
|
||||
const WAIT_TIME = 100; // ms
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { RecordingsListView, RecordingUtils } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield idleWait(WAIT_TIME); // allow refresh driver ticks to accumulate
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample more cpu activity
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
let recordingData = RecordingsListView.selectedItem.attachment;
|
||||
ok(recordingData, "The recording data was successfully retrieved.");
|
||||
|
||||
// Test plotting categories...
|
||||
|
||||
let profilerData = recordingData.profilerData;
|
||||
let categoriesData = RecordingUtils.plotCategoriesFor(profilerData, 0, Infinity);
|
||||
|
||||
for (let category of categoriesData) {
|
||||
is(Object.keys(category).toSource(), '["delta", "values"]',
|
||||
"The correct keys are in the cateogries data.");
|
||||
is(typeof category.delta, "number",
|
||||
"The delta property is a number, as expected.");
|
||||
is(typeof category.values, "object",
|
||||
"The values property is a object, as expected.");
|
||||
ok("length" in category.values,
|
||||
"The values property is an array, as expected.");
|
||||
}
|
||||
|
||||
// Test plotting framerate...
|
||||
|
||||
let ticksData = recordingData.ticksData;
|
||||
let framerateData = RecordingUtils.plotFramerateFor(ticksData, 0, Infinity);
|
||||
|
||||
ok("length" in framerateData,
|
||||
"The framerate data is an array, as expected.");
|
||||
|
||||
for (let rate of framerateData) {
|
||||
is(typeof rate.delta, "number",
|
||||
"The delta property is a number, as expected.");
|
||||
is(typeof rate.value, "number",
|
||||
"The value property is a number, as expected.");
|
||||
}
|
||||
|
||||
// Test categories and framerate syncing...
|
||||
|
||||
RecordingUtils.syncCategoriesWithFramerate(categoriesData, framerateData);
|
||||
info("Total framerate data: " + framerateData.length);
|
||||
info("Total categories data: " + categoriesData.length);
|
||||
|
||||
if (framerateData.length >= 2 && categoriesData.length >= 2) {
|
||||
is(categoriesData[0].delta, framerateData[0].delta,
|
||||
"The categories and framerate data now start at the exact same time.");
|
||||
is(categoriesData.pop().delta, framerateData.pop().delta,
|
||||
"The categories and framerate data now end at the exact same time.");
|
||||
}
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,50 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler is able clear all recordings.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, gFront, EVENTS, RecordingsListView, ProfileView } = panel.panelWin;
|
||||
|
||||
// Start and finish a manual recording.
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
// Start and finish a console recording.
|
||||
yield consoleProfile(gFront, "test 1");
|
||||
yield consoleProfileEnd(gFront, { waitForDisplayOn: panel });
|
||||
|
||||
// Start a new manual and console recording, but keep them in progress.
|
||||
yield startRecording(panel);
|
||||
yield consoleProfile(gFront, "test 2");
|
||||
|
||||
is(RecordingsListView.itemCount, 4,
|
||||
"There should be four recordings visible now.");
|
||||
is(RecordingsListView.selectedIndex, 1,
|
||||
"The second recording should be selected.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should be displayed in the profile view.");
|
||||
|
||||
let whenRecordingLost = panel.panelWin.once(EVENTS.RECORDING_LOST);
|
||||
EventUtils.synthesizeMouseAtCenter($("#clear-button"), {}, panel.panelWin);
|
||||
yield whenRecordingLost;
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(front, label) {
|
||||
let notified = front.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(front, { waitForDisplayOn: panel }) {
|
||||
let notified = front.once("profileEnd");
|
||||
let displayed = panel.panelWin.once(panel.panelWin.EVENTS.RECORDING_DISPLAYED);
|
||||
console.profileEnd();
|
||||
yield promise.all([notified, displayed]);
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler is able to save and load recordings.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { RecordingsListView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
// Verify original recording.
|
||||
|
||||
is(RecordingsListView.itemCount, 1,
|
||||
"There should be one recording visible now.");
|
||||
|
||||
let originalRecordingItem = RecordingsListView.getItemAtIndex(0);
|
||||
ok(originalRecordingItem,
|
||||
"A recording item was available for import.");
|
||||
|
||||
let originalData = originalRecordingItem.attachment;
|
||||
ok(originalData,
|
||||
"The original item has recording data attached to it.");
|
||||
|
||||
// Save recording.
|
||||
|
||||
let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
|
||||
|
||||
yield panel.panelWin.saveRecordingToFile(originalRecordingItem, file);
|
||||
ok(true, "The recording data appears to have been successfully saved.");
|
||||
|
||||
// Import recording.
|
||||
|
||||
yield panel.panelWin.loadRecordingFromFile(file);
|
||||
ok(true, "The recording data appears to have been successfully imported.");
|
||||
|
||||
// Verify imported recording.
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should be two recordings visible now.");
|
||||
|
||||
let importedRecordingItem = RecordingsListView.getItemAtIndex(1);
|
||||
ok(importedRecordingItem,
|
||||
"A recording item was imported.");
|
||||
|
||||
let importedData = importedRecordingItem.attachment;
|
||||
ok(importedData,
|
||||
"The imported item has recording data attached to it.");
|
||||
|
||||
ok(("fileType" in originalData) && ("fileType" in importedData),
|
||||
"The serialization process attached an identifier to the recording data.");
|
||||
ok(("version" in originalData) && ("version" in importedData),
|
||||
"The serialization process attached a version to the recording data.");
|
||||
|
||||
is(importedData.toSource(), originalData.toSource(),
|
||||
"The impored data is identical to the original data.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,28 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler gracefully handles loading bogus files.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { RecordingsListView } = panel.panelWin;
|
||||
|
||||
let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
|
||||
|
||||
try {
|
||||
yield panel.panelWin.loadRecordingFromFile(file);
|
||||
ok(false, "The recording succeeded unexpectedly.");
|
||||
} catch (e) {
|
||||
is(e.message, "Could not read recording data file.");
|
||||
ok(true, "The recording was cancelled.");
|
||||
}
|
||||
|
||||
is(RecordingsListView.itemCount, 0,
|
||||
"There should still be no recordings visible.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,57 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler gracefully handles loading files that are JSON,
|
||||
* but don't contain the appropriate recording data.
|
||||
*/
|
||||
|
||||
let { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
|
||||
let { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { RecordingsListView } = panel.panelWin;
|
||||
|
||||
let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
|
||||
yield asyncCopy({ bogus: "data" }, file);
|
||||
|
||||
try {
|
||||
yield panel.panelWin.loadRecordingFromFile(file);
|
||||
ok(false, "The recording succeeded unexpectedly.");
|
||||
} catch (e) {
|
||||
is(e.message, "Unrecognized recording data file.");
|
||||
ok(true, "The recording was cancelled.");
|
||||
}
|
||||
|
||||
is(RecordingsListView.itemCount, 0,
|
||||
"There should still be no recordings visible.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function getUnicodeConverter() {
|
||||
let className = "@mozilla.org/intl/scriptableunicodeconverter";
|
||||
let converter = Cc[className].createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
return converter;
|
||||
}
|
||||
|
||||
function asyncCopy(data, file) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let string = JSON.stringify(data);
|
||||
let inputStream = getUnicodeConverter().convertToInputStream(string);
|
||||
let outputStream = FileUtils.openSafeFileOutputStream(file);
|
||||
|
||||
NetUtil.asyncCopy(inputStream, outputStream, status => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
deferred.reject(new Error("Could not save data to file."));
|
||||
}
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
@ -1,55 +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 temp = {};
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", temp);
|
||||
let DebuggerServer = temp.DebuggerServer;
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm", temp);
|
||||
let DebuggerClient = temp.DebuggerClient;
|
||||
let debuggerSocketConnect = temp.debuggerSocketConnect;
|
||||
|
||||
Cu.import("resource:///modules/devtools/profiler/controller.js", temp);
|
||||
let ProfilerController = temp.ProfilerController;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
Services.prefs.setBoolPref(REMOTE_ENABLED, true);
|
||||
|
||||
loadTab(URL, function onTabLoad(tab, browser) {
|
||||
DebuggerServer.init(function () true);
|
||||
DebuggerServer.addBrowserActors();
|
||||
is(DebuggerServer.listeningSockets, 0);
|
||||
|
||||
DebuggerServer.openListener(2929);
|
||||
is(DebuggerServer.listeningSockets, 1);
|
||||
|
||||
let transport = debuggerSocketConnect("127.0.0.1", 2929);
|
||||
let client = new DebuggerClient(transport);
|
||||
client.connect(function onClientConnect() {
|
||||
let target = { isRemote: true, client: client };
|
||||
let controller = new ProfilerController(target);
|
||||
|
||||
controller.connect(function onControllerConnect() {
|
||||
// If this callback is called, this means listTabs call worked.
|
||||
// Which means that the transport worked. Time to finish up this
|
||||
// test.
|
||||
|
||||
function onShutdown() {
|
||||
window.removeEventListener("Debugger:Shutdown", onShutdown, true);
|
||||
transport = client = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
window.addEventListener("Debugger:Shutdown", onShutdown, true);
|
||||
|
||||
client.close(function () {
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,119 +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;
|
||||
|
||||
function done() {
|
||||
tearDown(gTab, () => { gPanel = null; gTab = null; });
|
||||
}
|
||||
|
||||
startRecording()
|
||||
.then(stopRecording)
|
||||
.then(startRecordingAgain)
|
||||
.then(stopRecording)
|
||||
.then(switchBackToTheFirstOne)
|
||||
.then(done);
|
||||
});
|
||||
}
|
||||
|
||||
function startRecording() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
ok(gPanel, "Profiler panel exists");
|
||||
ok(!gPanel.activeProfile, "Active profile doesn't exist");
|
||||
ok(!gPanel.recordingProfile, "Recording profile doesn't exist");
|
||||
|
||||
let record = gPanel.controls.record;
|
||||
ok(record, "Record button exists.");
|
||||
ok(!record.getAttribute("checked"), "Record button is unchecked");
|
||||
|
||||
gPanel.once("started", () => {
|
||||
let item = gPanel.sidebar.getItemByProfile(gPanel.recordingProfile);
|
||||
is(item.attachment.name, "Profile 1");
|
||||
is(item.attachment.state, PROFILE_RUNNING);
|
||||
is(record.getAttribute("tooltiptext"), "Stop profiling");
|
||||
|
||||
gPanel.controller.isActive(function (err, isActive) {
|
||||
ok(isActive, "Profiler is running");
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
record.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function stopRecording() {
|
||||
let deferred = promise.defer();
|
||||
let record = gPanel.controls.record;
|
||||
|
||||
gPanel.once("parsed", () => {
|
||||
let item = gPanel.sidebar.getItemByProfile(gPanel.activeProfile);
|
||||
is(item.attachment.state, PROFILE_COMPLETED);
|
||||
is(record.getAttribute("tooltiptext"), "Start profiling");
|
||||
|
||||
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);
|
||||
is(record.getAttribute("tooltiptext"), "Stop profiling");
|
||||
|
||||
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;
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if there's only one shared profiler connection instance per toolbox.
|
||||
*/
|
||||
|
||||
let gProfilerConnections = 0;
|
||||
Services.obs.addObserver(profilerConnectionObserver, "profiler-connection-created", false);
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let firstTab = yield addTab(SIMPLE_URL);
|
||||
let firstTarget = TargetFactory.forTab(firstTab);
|
||||
yield firstTarget.makeRemote();
|
||||
|
||||
yield gDevTools.showToolbox(firstTarget, "webconsole");
|
||||
is(gProfilerConnections, 1,
|
||||
"A shared profiler connection should have been created.");
|
||||
|
||||
yield gDevTools.showToolbox(firstTarget, "jsprofiler");
|
||||
is(gProfilerConnections, 1,
|
||||
"No new profiler connections should have been created.");
|
||||
|
||||
let secondTab = yield addTab(SIMPLE_URL);
|
||||
let secondTarget = TargetFactory.forTab(secondTab);
|
||||
yield secondTarget.makeRemote();
|
||||
|
||||
yield gDevTools.showToolbox(secondTarget, "jsprofiler");
|
||||
is(gProfilerConnections, 2,
|
||||
"Only one new profiler connection should have been created.");
|
||||
|
||||
yield removeTab(firstTab);
|
||||
yield removeTab(secondTab);
|
||||
|
||||
finish();
|
||||
});
|
||||
|
||||
function profilerConnectionObserver(subject, topic, data) {
|
||||
is(topic, "profiler-connection-created", "The correct topic was observed.");
|
||||
gProfilerConnections++;
|
||||
}
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.obs.removeObserver(profilerConnectionObserver, "profiler-connection-created");
|
||||
});
|
@ -0,0 +1,49 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the shared profiler connection is only opened once.
|
||||
*/
|
||||
|
||||
let gProfilerConnectionsOpened = 0;
|
||||
Services.obs.addObserver(profilerConnectionObserver, "profiler-connection-opened", false);
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
|
||||
is(gProfilerConnectionsOpened, 1,
|
||||
"Only one profiler connection was opened.");
|
||||
|
||||
let SharedProfilerUtils = devtools.require("devtools/profiler/shared");
|
||||
let sharedProfilerConnection = SharedProfilerUtils.getProfilerConnection(panel._toolbox);
|
||||
|
||||
ok(sharedProfilerConnection,
|
||||
"A shared profiler connection for the current toolbox was retrieved.");
|
||||
is(sharedProfilerConnection._request, panel.panelWin.gFront._request,
|
||||
"The same shared profiler connection is used by the panel's front.");
|
||||
|
||||
ok(sharedProfilerConnection._target,
|
||||
"A target exists for the current profiler connection.");
|
||||
ok(sharedProfilerConnection._client,
|
||||
"A target exists for the current profiler connection.");
|
||||
is(sharedProfilerConnection._pendingConsoleRecordings.length, 0,
|
||||
"There shouldn't be any pending console recordings yet.");
|
||||
is(sharedProfilerConnection._finishedConsoleRecordings.length, 0,
|
||||
"There shouldn't be any finished console recordings yet.");
|
||||
|
||||
yield sharedProfilerConnection.open();
|
||||
is(gProfilerConnectionsOpened, 1,
|
||||
"No additional profiler connections were opened.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function profilerConnectionObserver(subject, topic, data) {
|
||||
is(topic, "profiler-connection-opened", "The correct topic was observed.");
|
||||
gProfilerConnectionsOpened++;
|
||||
}
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.obs.removeObserver(profilerConnectionObserver, "profiler-connection-opened");
|
||||
});
|
@ -0,0 +1,29 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the shared profiler connection can properly send requests.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
ok(!nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should not have been automatically started.");
|
||||
|
||||
let result = yield front._request("profiler", "startProfiler");
|
||||
is(result.started, true,
|
||||
"The request finished successfully and the profiler should've been started.");
|
||||
ok(nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should now be active.");
|
||||
|
||||
let result = yield front._request("profiler", "stopProfiler");
|
||||
is(result.started, false,
|
||||
"The request finished successfully and the profiler should've been stopped.");
|
||||
ok(!nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should now be inactive.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,200 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the shared profiler connection's console notifications are
|
||||
* handled as expected.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
// This test seems to be a bit slow on debug builds.
|
||||
requestLongerTimeout(3);
|
||||
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
|
||||
let SharedProfilerUtils = devtools.require("devtools/profiler/shared");
|
||||
let sharedProfilerConnection = SharedProfilerUtils.getProfilerConnection(panel._toolbox);
|
||||
|
||||
let pendingRecordings = sharedProfilerConnection._pendingConsoleRecordings;
|
||||
let finishedRecordings = sharedProfilerConnection._finishedConsoleRecordings;
|
||||
let stackSize = 0;
|
||||
sharedProfilerConnection.on("profile", () => stackSize++);
|
||||
sharedProfilerConnection.on("profileEnd", () => stackSize--);
|
||||
|
||||
ok(!(yield sharedProfilerConnection._request("profiler", "isActive")).isActive,
|
||||
"The profiler should not be active yet.");
|
||||
ok(!(yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should not be recording yet.");
|
||||
|
||||
// Start calling `console.profile()`...
|
||||
|
||||
let pushedLabels = [];
|
||||
pushedLabels.push((yield consoleProfile(sharedProfilerConnection)));
|
||||
|
||||
// Quickly check if the profiler and framerate actor have started recording.
|
||||
|
||||
ok((yield sharedProfilerConnection._request("profiler", "isActive")).isActive,
|
||||
"The profiler should have been started.");
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should have started recording.");
|
||||
|
||||
// Continue calling `console.profile()` with different arguments...
|
||||
|
||||
pushedLabels.push((yield consoleProfile(sharedProfilerConnection, true, undefined)));
|
||||
pushedLabels.push((yield consoleProfile(sharedProfilerConnection, true, null)));
|
||||
pushedLabels.push((yield consoleProfile(sharedProfilerConnection, true, 0)));
|
||||
pushedLabels.push((yield consoleProfile(sharedProfilerConnection, true, "")));
|
||||
pushedLabels.push((yield consoleProfile(sharedProfilerConnection, true, [])));
|
||||
pushedLabels.push((yield consoleProfile(sharedProfilerConnection, true, [1, 2, 3])));
|
||||
pushedLabels.push((yield consoleProfile(sharedProfilerConnection, true, [4, 5, 6])));
|
||||
pushedLabels.push((yield consoleProfile(sharedProfilerConnection, true, "hello world")));
|
||||
pushedLabels.push((yield consoleProfile(sharedProfilerConnection, true, "hello world")));
|
||||
|
||||
// Verify the actors' and stack state.
|
||||
|
||||
ok((yield sharedProfilerConnection._request("profiler", "isActive")).isActive,
|
||||
"The profiler should still be active.");
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be active.");
|
||||
|
||||
is(pushedLabels.length, 10,
|
||||
"There should be 10 calls made to `console.profile()`.");
|
||||
is(pushedLabels[0], undefined,
|
||||
"The first `console.profile()` call had the correct coerced argument.");
|
||||
is(pushedLabels[1], "null",
|
||||
"The second `console.profile()` call had the correct coerced argument.");
|
||||
is(pushedLabels[2], "null",
|
||||
"The third `console.profile()` call had the correct coerced argument.");
|
||||
is(pushedLabels[3], "0",
|
||||
"The fourth `console.profile()` call had the correct coerced argument.");
|
||||
is(pushedLabels[4], "",
|
||||
"The fifth `console.profile()` call had the correct coerced argument.");
|
||||
is(pushedLabels[5], "",
|
||||
"The sixth `console.profile()` call had the correct coerced argument.");
|
||||
is(pushedLabels[6], "1,2,3",
|
||||
"The seventh `console.profile()` call had the correct coerced argument.");
|
||||
is(pushedLabels[7], "4,5,6",
|
||||
"The eigth `console.profile()` call had the correct coerced argument.");
|
||||
is(pushedLabels[8], "hello world",
|
||||
"The ninth `console.profile()` call had the correct coerced argument.");
|
||||
is(pushedLabels[9], "hello world",
|
||||
"The tenth `console.profile()` call had the correct coerced argument.");
|
||||
|
||||
is(stackSize, 7,
|
||||
"There should be 7 pending profiles on the stack.");
|
||||
is(pendingRecordings.length, 7,
|
||||
"The internal pending console recordings count is correct.");
|
||||
is(finishedRecordings.length, 0,
|
||||
"The internal finished console recordings count is correct.");
|
||||
|
||||
testPendingRecording(pendingRecordings, 0, pushedLabels[0]);
|
||||
testPendingRecording(pendingRecordings, 1, pushedLabels[1]);
|
||||
testPendingRecording(pendingRecordings, 2, pushedLabels[3]);
|
||||
testPendingRecording(pendingRecordings, 3, pushedLabels[4]);
|
||||
testPendingRecording(pendingRecordings, 4, pushedLabels[6]);
|
||||
testPendingRecording(pendingRecordings, 5, pushedLabels[7]);
|
||||
testPendingRecording(pendingRecordings, 6, pushedLabels[8]);
|
||||
|
||||
// Start calling `console.profileEnd()`...
|
||||
|
||||
let poppedLabels = [];
|
||||
poppedLabels.push((yield consoleProfileEnd(sharedProfilerConnection)));
|
||||
|
||||
// Quickly check if the profiler and framerate actor are still recording.
|
||||
|
||||
ok((yield sharedProfilerConnection._request("profiler", "isActive")).isActive,
|
||||
"The profiler should still be active after one recording stopped.");
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be active after one recording stopped.");
|
||||
|
||||
// Continue calling `console.profileEnd()` with different arguments...
|
||||
|
||||
poppedLabels.push((yield consoleProfileEnd(sharedProfilerConnection, true, null)));
|
||||
poppedLabels.push((yield consoleProfileEnd(sharedProfilerConnection, true, 0)));
|
||||
poppedLabels.push((yield consoleProfileEnd(sharedProfilerConnection, true, "")));
|
||||
poppedLabels.push((yield consoleProfileEnd(sharedProfilerConnection, true, [1, 2, 3])));
|
||||
poppedLabels.push((yield consoleProfileEnd(sharedProfilerConnection, true, [4, 5, 6])));
|
||||
poppedLabels.push((yield consoleProfileEnd(sharedProfilerConnection, true, "hello world")));
|
||||
|
||||
// Verify the actors' and stack state.
|
||||
|
||||
ok((yield sharedProfilerConnection._request("profiler", "isActive")).isActive,
|
||||
"The profiler should still be active after all recordings stopped.");
|
||||
ok(!(yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should not be active after all recordings stopped.");
|
||||
|
||||
is(poppedLabels.length, 7,
|
||||
"There should be 7 calls made to `console.profileEnd()`.");
|
||||
is(poppedLabels[0], undefined,
|
||||
"The first `console.profileEnd()` call had the correct coerced argument.");
|
||||
is(poppedLabels[1], "null",
|
||||
"The second `console.profileEnd()` call had the correct coerced argument.");
|
||||
is(poppedLabels[2], "0",
|
||||
"The third `console.profileEnd()` call had the correct coerced argument.");
|
||||
is(poppedLabels[3], "",
|
||||
"The fourth `console.profileEnd()` call had the correct coerced argument.");
|
||||
is(poppedLabels[4], "1,2,3",
|
||||
"The fifth `console.profileEnd()` call had the correct coerced argument.");
|
||||
is(poppedLabels[5], "4,5,6",
|
||||
"The sixth `console.profileEnd()` call had the correct coerced argument.");
|
||||
is(poppedLabels[6], "hello world",
|
||||
"The seventh `console.profileEnd()` call had the correct coerced argument.");
|
||||
|
||||
is(stackSize, 0,
|
||||
"There should be 0 pending profiles on the stack.");
|
||||
is(pendingRecordings.length, 0,
|
||||
"The internal pending console recordings count is correct.");
|
||||
is(finishedRecordings.length, 7,
|
||||
"The internal finished console recordings count is correct.");
|
||||
|
||||
testFinishedRecording(finishedRecordings, 0, poppedLabels[0]);
|
||||
testFinishedRecording(finishedRecordings, 1, poppedLabels[1]);
|
||||
testFinishedRecording(finishedRecordings, 2, poppedLabels[2]);
|
||||
testFinishedRecording(finishedRecordings, 3, poppedLabels[3]);
|
||||
testFinishedRecording(finishedRecordings, 4, poppedLabels[4]);
|
||||
testFinishedRecording(finishedRecordings, 5, poppedLabels[5]);
|
||||
testFinishedRecording(finishedRecordings, 6, poppedLabels[6]);
|
||||
|
||||
// We're done here.
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(connection, withLabel, labelValue) {
|
||||
let notified = connection.once("invoked-console-profile");
|
||||
if (!withLabel) {
|
||||
console.profile();
|
||||
} else {
|
||||
console.profile(labelValue);
|
||||
}
|
||||
return (yield notified);
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(connection, withLabel, labelValue) {
|
||||
let notified = connection.once("invoked-console-profileEnd");
|
||||
if (!withLabel) {
|
||||
console.profileEnd();
|
||||
} else {
|
||||
console.profileEnd(labelValue);
|
||||
}
|
||||
return (yield notified);
|
||||
}
|
||||
|
||||
function testPendingRecording(pendingRecordings, index, label) {
|
||||
is(pendingRecordings[index].profileLabel, label,
|
||||
"The pending profile at index " + index + " on the stack has the correct label.");
|
||||
ok(pendingRecordings[index].profilingStartTime >= 0,
|
||||
"...and has a positive start time.");
|
||||
}
|
||||
|
||||
function testFinishedRecording(finishedRecordings, index, label) {
|
||||
is(finishedRecordings[index].profilerData.profileLabel, label,
|
||||
"The finished profile at index " + index + " has the correct label.");
|
||||
ok(finishedRecordings[index].recordingDuration > 0,
|
||||
"...and has a strictly positive recording duration.");
|
||||
ok("samples" in finishedRecordings[index].profilerData.profile.threads[0],
|
||||
"...with profiler data samples attached.");
|
||||
ok("ticksData" in finishedRecordings[index],
|
||||
"...and with refresh driver ticks data attached.");
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler connection front relays console notifications.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
// This test seems to be a bit slow on debug builds.
|
||||
requestLongerTimeout(3);
|
||||
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
let SharedProfilerUtils = devtools.require("devtools/profiler/shared");
|
||||
let sharedProfilerConnection = SharedProfilerUtils.getProfilerConnection(panel._toolbox);
|
||||
|
||||
let stackSize = 0;
|
||||
front.on("profile", () => stackSize++);
|
||||
front.on("profileEnd", () => stackSize--);
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
yield consoleProfile(sharedProfilerConnection, i);
|
||||
is(stackSize, i + 1,
|
||||
"The current stack size is correctly: " + (i + 1));
|
||||
is(front.pendingConsoleRecordings.length, i + 1,
|
||||
"The publicly exposed pending recordings array has the correct size.");
|
||||
}
|
||||
for (let i = 9; i >= 0; i--) {
|
||||
yield consoleProfileEnd(sharedProfilerConnection);
|
||||
is(stackSize, i,
|
||||
"The current stack size is correctly: " + i);
|
||||
is(front.pendingConsoleRecordings.length, i,
|
||||
"The publicly exposed pending recordings array has the correct size.");
|
||||
is(front.finishedConsoleRecordings.length, 10 - i,
|
||||
"The publicly exposed finished recordings array has the correct size.");
|
||||
}
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(connection, label) {
|
||||
let notified = connection.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(connection) {
|
||||
let notified = connection.once("profileEnd");
|
||||
console.profileEnd();
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler connection front does not activate the built-in
|
||||
* profiler module if not necessary, and doesn't deactivate it when
|
||||
* a recording is stopped.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
ok(!nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should not have been automatically started.");
|
||||
|
||||
let activated = front.once("profiler-activated");
|
||||
yield front.startRecording();
|
||||
yield activated;
|
||||
yield front.stopRecording();
|
||||
ok(nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should still be active (1).");
|
||||
|
||||
let alreadyActive = front.once("profiler-already-active");
|
||||
yield front.startRecording();
|
||||
yield alreadyActive;
|
||||
yield front.stopRecording();
|
||||
ok(nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should still be active (2).");
|
||||
|
||||
yield teardown(panel);
|
||||
|
||||
ok(!nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should have been automatically stoped.");
|
||||
|
||||
finish();
|
||||
});
|
@ -0,0 +1,33 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the built-in profiler module doesn't deactivate when the toolbox
|
||||
* is destroyed if there are other consumers using it.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [,, firstPanel] = yield initFrontend(SIMPLE_URL);
|
||||
let firstFront = firstPanel.panelWin.gFront;
|
||||
|
||||
let activated = firstFront.once("profiler-activated");
|
||||
yield firstFront.startRecording();
|
||||
yield activated;
|
||||
|
||||
let [,, secondPanel] = yield initFrontend(SIMPLE_URL);
|
||||
let secondFront = secondPanel.panelWin.gFront;
|
||||
|
||||
let alreadyActive = secondFront.once("profiler-already-active");
|
||||
yield secondFront.startRecording();
|
||||
yield alreadyActive;
|
||||
|
||||
yield teardown(firstPanel);
|
||||
ok(nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should still be active.");
|
||||
|
||||
yield teardown(secondPanel);
|
||||
ok(!nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should have been automatically stoped.");
|
||||
|
||||
finish();
|
||||
});
|
@ -0,0 +1,43 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the built-in profiler module is not reactivated if no other
|
||||
* consumer was using it over the remote debugger protocol, and ensures
|
||||
* that the actor will work properly even in such cases (e.g. the Gecko Profiler
|
||||
* addon was installed and automatically activated the profiler module).
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
// Ensure the profiler is already running when the test starts.
|
||||
let ENTRIES = 1000000;
|
||||
let INTERVAL = 1;
|
||||
let FEATURES = ["js"];
|
||||
nsIProfilerModule.StartProfiler(ENTRIES, INTERVAL, FEATURES, FEATURES.length);
|
||||
|
||||
let [,, firstPanel] = yield initFrontend(SIMPLE_URL);
|
||||
let firstFront = firstPanel.panelWin.gFront;
|
||||
|
||||
let alredyActive = firstFront.once("profiler-already-active");
|
||||
yield firstFront.startRecording();
|
||||
yield alredyActive;
|
||||
ok(firstFront._profilingStartTime > 0, "The profiler was not restarted.");
|
||||
|
||||
let [,, secondPanel] = yield initFrontend(SIMPLE_URL);
|
||||
let secondFront = secondPanel.panelWin.gFront;
|
||||
|
||||
let alreadyActive = secondFront.once("profiler-already-active");
|
||||
yield secondFront.startRecording();
|
||||
yield alreadyActive;
|
||||
ok(secondFront._profilingStartTime > 0, "The profiler was not restarted.");
|
||||
|
||||
yield teardown(firstPanel);
|
||||
ok(nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should still be active.");
|
||||
|
||||
yield teardown(secondPanel);
|
||||
ok(!nsIProfilerModule.IsActive(),
|
||||
"The built-in profiler module should have been automatically stoped.");
|
||||
|
||||
finish();
|
||||
});
|
@ -0,0 +1,64 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the framerate front is kept recording only when required.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [,, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
let SharedProfilerUtils = devtools.require("devtools/profiler/shared");
|
||||
let sharedProfilerConnection = SharedProfilerUtils.getProfilerConnection(panel._toolbox);
|
||||
|
||||
ok(!(yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should not be recording yet.");
|
||||
|
||||
yield front.startRecording();
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should now be recording.");
|
||||
|
||||
yield consoleProfile(sharedProfilerConnection, "1");
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be recording (1).");
|
||||
|
||||
yield front.startRecording();
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be recording (2).");
|
||||
|
||||
yield consoleProfile(sharedProfilerConnection, "2");
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be recording (3).");
|
||||
|
||||
yield front.stopRecording();
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be recording (4).");
|
||||
|
||||
yield consoleProfileEnd(sharedProfilerConnection);
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be recording (5).");
|
||||
|
||||
yield front.stopRecording();
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be recording (6).");
|
||||
|
||||
yield consoleProfileEnd(sharedProfilerConnection);
|
||||
ok(!(yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should finally have stopped recording.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(connection, label) {
|
||||
let notified = connection.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(connection) {
|
||||
let notified = connection.once("profileEnd");
|
||||
console.profileEnd();
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the framerate front is kept recording only when required,
|
||||
* this time doing everything in reverse.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [,, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
let SharedProfilerUtils = devtools.require("devtools/profiler/shared");
|
||||
let sharedProfilerConnection = SharedProfilerUtils.getProfilerConnection(panel._toolbox);
|
||||
|
||||
ok(!(yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should not be recording yet.");
|
||||
|
||||
yield consoleProfile(sharedProfilerConnection, "1");
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should now be recording.");
|
||||
|
||||
yield front.startRecording();
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be recording (1).");
|
||||
|
||||
yield consoleProfile(sharedProfilerConnection, "2");
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be recording (2).");
|
||||
|
||||
yield front.startRecording();
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be recording (3).");
|
||||
|
||||
yield consoleProfileEnd(sharedProfilerConnection);
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be recording (4).");
|
||||
|
||||
yield front.stopRecording();
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be recording (5).");
|
||||
|
||||
yield consoleProfileEnd(sharedProfilerConnection);
|
||||
ok((yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should still be recording (6).");
|
||||
|
||||
yield front.stopRecording();
|
||||
ok(!(yield sharedProfilerConnection._framerate.isRecording()),
|
||||
"The framerate actor should finally have stopped recording.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(connection, label) {
|
||||
let notified = connection.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
||||
|
||||
function* consoleProfileEnd(connection) {
|
||||
let notified = connection.once("profileEnd");
|
||||
console.profileEnd();
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler is able to start a simple recording.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, L10N, RecordingsListView, ProfileView } = panel.panelWin;
|
||||
|
||||
is(RecordingsListView.itemCount, 0,
|
||||
"There should be no recordings visible yet.");
|
||||
ok($(".side-menu-widget-empty-text"),
|
||||
"There should be some empty text displayed in the recordings list.");
|
||||
|
||||
is(ProfileView.tabCount, 0,
|
||||
"There shouldn't be any tabs visible yet.");
|
||||
is($("#profile-pane").selectedPanel, $("#empty-notice"),
|
||||
"There should be an empty notice displayed in the profile view.");
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
is(RecordingsListView.itemCount, 1,
|
||||
"There should be one recording visible now.");
|
||||
ok(!$(".side-menu-widget-empty-text"),
|
||||
"There shouldn't be any empty text displayed in the recordings list.");
|
||||
|
||||
is(ProfileView.tabCount, 0,
|
||||
"There still shouldn't be any tabs visible yet.");
|
||||
is($("#profile-pane").selectedPanel, $("#recording-notice"),
|
||||
"There should be a recording notice displayed in the profile view.");
|
||||
|
||||
let recordingItem = RecordingsListView.selectedItem;
|
||||
is(recordingItem, RecordingsListView.items[0],
|
||||
"The first and only recording item should be automatically selected.");
|
||||
|
||||
is($(".recording-item-title", recordingItem.target).getAttribute("value"),
|
||||
L10N.getFormatStr("recordingsList.itemLabel", 1),
|
||||
"The recording item's title is correct.");
|
||||
|
||||
is($(".recording-item-duration", recordingItem.target).getAttribute("value"),
|
||||
L10N.getStr("recordingsList.recordingLabel"),
|
||||
"The recording item's duration is correct.");
|
||||
|
||||
is($(".recording-item-save", recordingItem.target).getAttribute("value"), "",
|
||||
"The recording item's save link should be invisible.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,52 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler is able to end a simple recording.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, L10N, RecordingsListView, ProfileView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
is(RecordingsListView.itemCount, 1,
|
||||
"There should be one recording visible.");
|
||||
ok(!$(".side-menu-widget-empty-text"),
|
||||
"There shouldn't be any empty text displayed in the recordings list.");
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should be one tab visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should be displayed in the profile view.");
|
||||
|
||||
let recordingItem = RecordingsListView.selectedItem;
|
||||
is(recordingItem, RecordingsListView.items[0],
|
||||
"The first and only recording item should be automatically selected.");
|
||||
|
||||
ok(recordingItem.attachment.recordingDuration > 0,
|
||||
"The recording duration appears to be correct.");
|
||||
is(recordingItem.attachment.profileLabel, undefined,
|
||||
"The profile label should be undefined for non-console recordings.");
|
||||
ok(recordingItem.attachment.profilerData,
|
||||
"The profiler data appears to be correct.");
|
||||
ok(recordingItem.attachment.ticksData,
|
||||
"The ticks data appears to be correct.");
|
||||
|
||||
is($(".recording-item-title", recordingItem.target).getAttribute("value"),
|
||||
L10N.getFormatStr("recordingsList.itemLabel", 1),
|
||||
"The recording item's title is correct.");
|
||||
|
||||
ok($(".recording-item-duration", recordingItem.target).getAttribute("value")
|
||||
.match(/\d+ ms/),
|
||||
"The recording item's duration is correct.");
|
||||
|
||||
is($(".recording-item-save", recordingItem.target).getAttribute("value"),
|
||||
L10N.getStr("recordingsList.saveLabel"),
|
||||
"The recording item's save link is correct.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,27 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler goes through the appropriate steps while displaying
|
||||
* a finished recording.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { EVENTS } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
let loadingNoticeDisplayed = panel.panelWin.once(EVENTS.LOADING_NOTICE_SHOWN);
|
||||
let recordingDisplayed = panel.panelWin.once(EVENTS.RECORDING_DISPLAYED);
|
||||
yield stopRecording(panel, { waitForDisplay: false });
|
||||
|
||||
yield loadingNoticeDisplayed;
|
||||
ok(true, "The loading noticed was briefly displayed.");
|
||||
|
||||
yield recordingDisplayed;
|
||||
ok(true, "The recording was finally displayed.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,43 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler removes all pending recordings in case of a
|
||||
* sudden deactivation.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, RecordingsListView } = panel.panelWin;
|
||||
|
||||
is(RecordingsListView.itemCount, 0,
|
||||
"There should be no recordings visible yet.");
|
||||
is($("#profile-pane").selectedPanel, $("#empty-notice"),
|
||||
"There should be an empty notice displayed in the profile view.");
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
is(RecordingsListView.itemCount, 1,
|
||||
"There should be one recording visible now.");
|
||||
is($("#profile-pane").selectedPanel, $("#recording-notice"),
|
||||
"There should be a recording notice displayed in the profile view.");
|
||||
|
||||
let whenLost = panel.panelWin.once(EVENTS.RECORDING_LOST);
|
||||
|
||||
// Forcibly stop the built-in profiler module.
|
||||
nsIProfilerModule.StopProfiler();
|
||||
|
||||
yield whenLost;
|
||||
ok(true, "Recording was cancelled in the frontend.");
|
||||
|
||||
is(RecordingsListView.itemCount, 0,
|
||||
"There should be no recordings visible now.");
|
||||
is($("#profile-pane").selectedPanel, $("#empty-notice"),
|
||||
"There should be an empty notice displayed in the profile view again.");
|
||||
|
||||
ok(!$("#record-button").hasAttribute("checked"),
|
||||
"The record button was unchecked.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,54 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler removes all pending recordings in case of a
|
||||
* sudden deactivation, even the ones started via `console.profile`.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, EVENTS, gFront, RecordingsListView } = panel.panelWin;
|
||||
|
||||
is(RecordingsListView.itemCount, 0,
|
||||
"There should be no recordings visible yet.");
|
||||
is($("#profile-pane").selectedPanel, $("#empty-notice"),
|
||||
"There should be an empty notice displayed in the profile view.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield consoleProfile(gFront, "test");
|
||||
|
||||
is(RecordingsListView.itemCount, 2,
|
||||
"There should be two recordings visible now.");
|
||||
is(RecordingsListView.selectedIndex, 0,
|
||||
"The first recording should be selected in the list.");
|
||||
is($("#profile-pane").selectedPanel, $("#recording-notice"),
|
||||
"There should be a recording notice displayed in the profile view.");
|
||||
|
||||
let whenLost = panel.panelWin.once(EVENTS.RECORDING_LOST);
|
||||
|
||||
// Forcibly stop the built-in profiler module.
|
||||
nsIProfilerModule.StopProfiler();
|
||||
|
||||
yield whenLost;
|
||||
ok(true, "Recording was cancelled in the frontend.");
|
||||
|
||||
is(RecordingsListView.itemCount, 0,
|
||||
"There should be no recordings visible now.");
|
||||
is(RecordingsListView.selectedIndex, -1,
|
||||
"There shouldn't be any recording selected in the list.");
|
||||
is($("#profile-pane").selectedPanel, $("#empty-notice"),
|
||||
"There should be an empty notice displayed in the profile view again.");
|
||||
|
||||
ok(!$("#record-button").hasAttribute("checked"),
|
||||
"The record button was unchecked.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function* consoleProfile(front, label) {
|
||||
let notified = front.once("profile");
|
||||
console.profile(label);
|
||||
yield notified;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler displays and organizes the recording data in tabs.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, ProfileView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
is(ProfileView.tabCount, 1,
|
||||
"There should be one tab visible.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should be displayed in the profile view.");
|
||||
is($("#profile-content").selectedIndex, 0,
|
||||
"The first tab is selected.");
|
||||
|
||||
ok($("#profile-content .tab-title-label").getAttribute("value")
|
||||
.match(/\d+ ms . \d+ ms/),
|
||||
"The recording's first tab title is correct.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,90 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler displays and organizes the recording data in tabs,
|
||||
* spawning a new tab from frame nodes when required.
|
||||
*/
|
||||
|
||||
const WAIT_TIME = 1000; // ms
|
||||
|
||||
let gPrevShowPlatformData =
|
||||
Services.prefs.getBoolPref("devtools.profiler.ui.show-platform-data");
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, $$, EVENTS, Prefs, ProfileView } = panel.panelWin;
|
||||
|
||||
// Make sure platform data is displayed, so there's samples available.
|
||||
Prefs.showPlatformData = true;
|
||||
|
||||
yield startRecording(panel);
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample more cpu activity
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
let treeRoot = ProfileView._getCallTreeRoot();
|
||||
ok(treeRoot,
|
||||
"There's a call tree view available on the profile view.");
|
||||
let callItem = treeRoot.getChild(0);
|
||||
ok(callItem,
|
||||
"The first displayed item in the tree was retreived.");
|
||||
let callNode = callItem.target;
|
||||
ok(callNode,
|
||||
"The first displayed item in the tree has a corresponding DOM node.");
|
||||
|
||||
is($(".call-tree-name", callNode).getAttribute("value"), "Startup::XRE_Main",
|
||||
"The first displayed item in the tree has the expected function name.");
|
||||
is($(".call-tree-cell[type=percentage]", callNode).getAttribute("value"), "100%",
|
||||
"The first displayed item in the tree has the expected percentage usage.");
|
||||
|
||||
let tabSpawned = panel.panelWin.once(EVENTS.TAB_SPAWNED_FROM_FRAME_NODE);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, callNode.querySelector(".call-tree-zoom"));
|
||||
yield tabSpawned;
|
||||
|
||||
is(ProfileView.tabCount, 2,
|
||||
"There should be two tabs visible now.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should still be displayed in the profile view.");
|
||||
is($("#profile-content").selectedIndex, 1,
|
||||
"The second tab is now selected.");
|
||||
|
||||
let firstTabTitle = $$("#profile-content .tab-title-label")[0].getAttribute("value");
|
||||
let secondTabTitle = $$("#profile-content .tab-title-label")[1].getAttribute("value");
|
||||
info("The first tab's title is: " + firstTabTitle);
|
||||
info("The second tab's title is: " + secondTabTitle);
|
||||
|
||||
isnot(firstTabTitle, secondTabTitle,
|
||||
"The first and second tab titles are different.");
|
||||
ok(firstTabTitle.match(/\d+ ms . \d+ ms/),
|
||||
"The recording's first tab title is correct.");
|
||||
ok(secondTabTitle.match(/[\d\.,]+ ms . [\d\.,]+ ms/),
|
||||
"The recording's second tab title is correct.");
|
||||
|
||||
let newTreeRoot = ProfileView._getCallTreeRoot();
|
||||
ok(newTreeRoot,
|
||||
"There's a call tree view available on the profile view again.");
|
||||
let newCallItem = newTreeRoot.getChild(0);
|
||||
ok(newCallItem,
|
||||
"The first displayed item in the tree was retreived again.");
|
||||
let newCallNode = newCallItem.target;
|
||||
ok(newCallNode,
|
||||
"The first displayed item in the tree has a corresponding DOM node again.");
|
||||
|
||||
isnot(treeRoot, newTreeRoot,
|
||||
"The new call tree view has a different root this time.");
|
||||
isnot(callNode, newCallNode,
|
||||
"The new call tree view has a different corresponding DOM node this time.");
|
||||
|
||||
is($(".call-tree-name", newTreeRoot.target).getAttribute("value"), "Startup::XRE_Main",
|
||||
"The first displayed item in the tree has the expected function name.");
|
||||
is($(".call-tree-cell[type=percentage]", newTreeRoot.target).getAttribute("value"), "100%",
|
||||
"The first displayed item in the tree has the expected percentage usage.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.setBoolPref(
|
||||
"devtools.profiler.ui.show-platform-data", gPrevShowPlatformData);
|
||||
});
|
@ -0,0 +1,88 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler displays and organizes the recording data in tabs,
|
||||
* spawning a new tab from a graph's selection when required.
|
||||
*/
|
||||
|
||||
const WAIT_TIME = 1000; // ms
|
||||
|
||||
let gPrevShowPlatformData =
|
||||
Services.prefs.getBoolPref("devtools.profiler.ui.show-platform-data");
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, $$, EVENTS, Prefs, ProfileView } = panel.panelWin;
|
||||
|
||||
// Make sure platform data is displayed, so there's samples available.
|
||||
Prefs.showPlatformData = true;
|
||||
|
||||
yield startRecording(panel);
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample more cpu activity
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
let categoriesGraph = ProfileView._getCategoriesGraph();
|
||||
ok(categoriesGraph,
|
||||
"There's a categories graph available on the profile view.");
|
||||
|
||||
ok(!categoriesGraph.hasSelection(),
|
||||
"The categories graph shouldn't have a selection available yet.");
|
||||
ok($("#profile-newtab-button").hidden,
|
||||
"The new tab button on the profile view tab bar should be hidden.");
|
||||
|
||||
dragStart(categoriesGraph, 10);
|
||||
dragStop(categoriesGraph, 50);
|
||||
|
||||
ok(categoriesGraph.hasSelection(),
|
||||
"The categories graph should have a selection available now.");
|
||||
ok(!$("#profile-newtab-button").hidden,
|
||||
"The new tab button on the profile view tab bar should be visible.");
|
||||
|
||||
let tabSpawned = panel.panelWin.once(EVENTS.TAB_SPAWNED_FROM_SELECTION);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#profile-newtab-button"));
|
||||
yield tabSpawned;
|
||||
|
||||
is(ProfileView.tabCount, 2,
|
||||
"There should be two tabs visible now.");
|
||||
is($("#profile-pane").selectedPanel, $("#profile-content"),
|
||||
"The profile content should still be displayed in the profile view.");
|
||||
is($("#profile-content").selectedIndex, 1,
|
||||
"The second tab is now selected.");
|
||||
|
||||
let firstTabTitle = $$("#profile-content .tab-title-label")[0].getAttribute("value");
|
||||
let secondTabTitle = $$("#profile-content .tab-title-label")[1].getAttribute("value");
|
||||
info("The first tab's title is: " + firstTabTitle);
|
||||
info("The second tab's title is: " + secondTabTitle);
|
||||
|
||||
isnot(firstTabTitle, secondTabTitle,
|
||||
"The first and second tab titles are different.");
|
||||
ok(firstTabTitle.match(/\d+ ms . \d+ ms/),
|
||||
"The recording's first tab title is correct.");
|
||||
ok(secondTabTitle.match(/[\d\.,]+ ms . [\d\.,]+ ms/),
|
||||
"The recording's second tab title is correct.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.setBoolPref(
|
||||
"devtools.profiler.ui.show-platform-data", gPrevShowPlatformData);
|
||||
});
|
||||
|
||||
// EventUtils just doesn't work!
|
||||
|
||||
function dragStart(graph, x, y = 1) {
|
||||
x /= window.devicePixelRatio;
|
||||
y /= window.devicePixelRatio;
|
||||
graph._onMouseMove({ clientX: x, clientY: y });
|
||||
graph._onMouseDown({ clientX: x, clientY: y });
|
||||
}
|
||||
|
||||
function dragStop(graph, x, y = 1) {
|
||||
x /= window.devicePixelRatio;
|
||||
y /= window.devicePixelRatio;
|
||||
graph._onMouseMove({ clientX: x, clientY: y });
|
||||
graph._onMouseUp({ clientX: x, clientY: y });
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if adding, naming and selecting tabs in the ProfileView works.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, $$, L10N, Prefs, ProfileView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
let tabIndex = ProfileView.addTab();
|
||||
is(tabIndex, 1,
|
||||
"A new tab was successfully added to the tabbed browser.");
|
||||
|
||||
is($("#profile-content tabs").childNodes.length, 2,
|
||||
"There should be two tabs in the tabbed browser.");
|
||||
is($("#profile-content tabpanels").childNodes.length, 2,
|
||||
"There should be two tabpanels in the tabbed browser.");
|
||||
|
||||
is($("#profile-content").selectedIndex, 0,
|
||||
"The first tab should still be selected in the tabbed browser (1).");
|
||||
is($("#profile-content").selectedTab, $$("#profile-content tab")[0],
|
||||
"The first tab should still be selected in the tabbed browser (2).");
|
||||
|
||||
ProfileView.nameTab(tabIndex, 12.34, 56.78);
|
||||
|
||||
ok($$("#profile-content .tab-title-label")[1].getAttribute("value"),
|
||||
L10N.getFormatStr("profile.tab", "12.34", "56.78"),
|
||||
"The newly added tab's title was successfully changed.");
|
||||
|
||||
ProfileView.selectTab(tabIndex);
|
||||
|
||||
is($("#profile-content").selectedIndex, 1,
|
||||
"The second tab is now selected in the tabbed browser (1).");
|
||||
is($("#profile-content").selectedTab, $$("#profile-content tab")[1],
|
||||
"The second tab is now selected in the tabbed browser (2).");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,55 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if removing tabs in the ProfileView works.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let [target, debuggee, panel] = yield initFrontend(SIMPLE_URL);
|
||||
let { $, $$, L10N, Prefs, ProfileView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel, { waitForDisplay: true });
|
||||
|
||||
let newTab1 = ProfileView.addTab();
|
||||
let newTab2 = ProfileView.addTab();
|
||||
let newTab3 = ProfileView.addTab();
|
||||
|
||||
is($("#profile-content tabs").childNodes.length, 4,
|
||||
"There should be four tabs in the tabbed browser.");
|
||||
is($("#profile-content tabpanels").childNodes.length, 4,
|
||||
"There should be four tabpanels in the tabbed browser.");
|
||||
|
||||
is($("#profile-content").selectedIndex, 0,
|
||||
"The first tab should still be selected in the tabbed browser (1).");
|
||||
is($("#profile-content").selectedTab, $$("#profile-content tab")[0],
|
||||
"The first tab should still be selected in the tabbed browser (2).");
|
||||
|
||||
ProfileView.removeTabsAfter(newTab1);
|
||||
|
||||
is($("#profile-content tabs").childNodes.length, 2,
|
||||
"There should be two tabs in the tabbed browser now.");
|
||||
is($("#profile-content tabpanels").childNodes.length, 2,
|
||||
"There should be two tabpanels in the tabbed browser now.");
|
||||
|
||||
is($("#profile-content").selectedIndex, 0,
|
||||
"The first tab should still be selected in the tabbed browser (3).");
|
||||
is($("#profile-content").selectedTab, $$("#profile-content tab")[0],
|
||||
"The first tab should still be selected in the tabbed browser (4).");
|
||||
|
||||
ProfileView.removeAllTabs();
|
||||
|
||||
is($("#profile-content tabs").childNodes.length, 0,
|
||||
"There should be no tabs in the tabbed browser now.");
|
||||
is($("#profile-content tabpanels").childNodes.length, 0,
|
||||
"There should be no tabpanels in the tabbed browser now.");
|
||||
|
||||
is($("#profile-content").selectedIndex, -1,
|
||||
"No tab should be selected in the tabbed browser (3).");
|
||||
is($("#profile-content").selectedTab, null,
|
||||
"No tab should be selected in the tabbed browser (4).");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,175 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the abstract tree base class for the profiler's tree view
|
||||
* works as advertised.
|
||||
*/
|
||||
|
||||
let { AbstractTreeItem } = Cu.import("resource:///modules/devtools/AbstractTreeItem.jsm", {});
|
||||
let { Heritage } = Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let container = document.createElement("vbox");
|
||||
gBrowser.selectedBrowser.parentNode.appendChild(container);
|
||||
|
||||
// Populate the tree and test the root item...
|
||||
|
||||
let treeRoot = new MyCustomTreeItem(gDataSrc, { parent: null });
|
||||
treeRoot.attachTo(container);
|
||||
|
||||
is(container.childNodes.length, 1,
|
||||
"The container node should have one child available.");
|
||||
is(container.childNodes[0], treeRoot.target,
|
||||
"The root node's target is a child of the container node.");
|
||||
|
||||
is(treeRoot.root, treeRoot,
|
||||
"The root node has the correct root.");
|
||||
is(treeRoot.parent, null,
|
||||
"The root node has the correct parent.");
|
||||
is(treeRoot.level, 0,
|
||||
"The root node has the correct level.");
|
||||
is(treeRoot.target.MozMarginStart, "0px",
|
||||
"The root node's indentation is correct.");
|
||||
is(treeRoot.target.textContent, "root",
|
||||
"The root node's text contents are correct.");
|
||||
is(treeRoot.container, container,
|
||||
"The root node's container is correct.");
|
||||
|
||||
// Expand the root and test the child items...
|
||||
|
||||
let receivedExpandEvent = treeRoot.once("expand");
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, treeRoot.target.querySelector(".arrow"));
|
||||
|
||||
let eventItem = yield receivedExpandEvent;
|
||||
is(eventItem, treeRoot,
|
||||
"The 'expand' event target is correct.");
|
||||
is(document.commandDispatcher.focusedElement, treeRoot.target,
|
||||
"The root node is now focused.");
|
||||
|
||||
let fooItem = treeRoot.getChild(0);
|
||||
let barItem = treeRoot.getChild(1);
|
||||
|
||||
is(container.childNodes.length, 3,
|
||||
"The container node should now have three children available.");
|
||||
is(container.childNodes[0], treeRoot.target,
|
||||
"The root node's target is a child of the container node.");
|
||||
is(container.childNodes[1], fooItem.target,
|
||||
"The 'foo' node's target is a child of the container node.");
|
||||
is(container.childNodes[2], barItem.target,
|
||||
"The 'bar' node's target is a child of the container node.");
|
||||
|
||||
is(fooItem.root, treeRoot,
|
||||
"The 'foo' node has the correct root.");
|
||||
is(fooItem.parent, treeRoot,
|
||||
"The 'foo' node has the correct parent.");
|
||||
is(fooItem.level, 1,
|
||||
"The 'foo' node has the correct level.");
|
||||
is(fooItem.target.MozMarginStart, "10px",
|
||||
"The 'foo' node's indentation is correct.");
|
||||
is(fooItem.target.textContent, "foo",
|
||||
"The 'foo' node's text contents are correct.");
|
||||
is(fooItem.container, container,
|
||||
"The 'foo' node's container is correct.");
|
||||
|
||||
is(barItem.root, treeRoot,
|
||||
"The 'bar' node has the correct root.");
|
||||
is(barItem.parent, treeRoot,
|
||||
"The 'bar' node has the correct parent.");
|
||||
is(barItem.level, 1,
|
||||
"The 'bar' node has the correct level.");
|
||||
is(barItem.target.MozMarginStart, "10px",
|
||||
"The 'bar' node's indentation is correct.");
|
||||
is(barItem.target.textContent, "bar",
|
||||
"The 'bar' node's text contents are correct.");
|
||||
is(barItem.container, container,
|
||||
"The 'bar' node's container is correct.");
|
||||
|
||||
// Test other events on the child nodes...
|
||||
|
||||
let receivedFocusEvent = treeRoot.once("focus");
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, fooItem.target);
|
||||
|
||||
let eventItem = yield receivedFocusEvent;
|
||||
is(eventItem, fooItem,
|
||||
"The 'focus' event target is correct.");
|
||||
is(document.commandDispatcher.focusedElement, fooItem.target,
|
||||
"The 'foo' node is now focused.");
|
||||
|
||||
let receivedDblClickEvent = treeRoot.once("focus");
|
||||
EventUtils.sendMouseEvent({ type: "dblclick" }, barItem.target);
|
||||
|
||||
let eventItem = yield receivedDblClickEvent;
|
||||
is(eventItem, barItem,
|
||||
"The 'dblclick' event target is correct.");
|
||||
is(document.commandDispatcher.focusedElement, barItem.target,
|
||||
"The 'bar' node is now focused.");
|
||||
|
||||
// A child item got expanded, test the descendants...
|
||||
|
||||
let bazItem = barItem.getChild(0);
|
||||
|
||||
is(container.childNodes.length, 4,
|
||||
"The container node should now have four children available.");
|
||||
is(container.childNodes[0], treeRoot.target,
|
||||
"The root node's target is a child of the container node.");
|
||||
is(container.childNodes[1], fooItem.target,
|
||||
"The 'foo' node's target is a child of the container node.");
|
||||
is(container.childNodes[2], barItem.target,
|
||||
"The 'bar' node's target is a child of the container node.");
|
||||
is(container.childNodes[3], bazItem.target,
|
||||
"The 'baz' node's target is a child of the container node.");
|
||||
|
||||
is(bazItem.root, treeRoot,
|
||||
"The 'baz' node has the correct root.");
|
||||
is(bazItem.parent, barItem,
|
||||
"The 'baz' node has the correct parent.");
|
||||
is(bazItem.level, 2,
|
||||
"The 'baz' node has the correct level.");
|
||||
is(bazItem.target.MozMarginStart, "20px",
|
||||
"The 'baz' node's indentation is correct.");
|
||||
is(bazItem.target.textContent, "baz",
|
||||
"The 'baz' node's text contents are correct.");
|
||||
is(bazItem.container, container,
|
||||
"The 'baz' node's container is correct.");
|
||||
|
||||
container.remove();
|
||||
finish();
|
||||
});
|
||||
|
||||
function MyCustomTreeItem(dataSrc, properties) {
|
||||
AbstractTreeItem.call(this, properties);
|
||||
this.itemDataSrc = dataSrc;
|
||||
}
|
||||
|
||||
MyCustomTreeItem.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
_displaySelf: function(document, arrowNode) {
|
||||
let node = document.createElement("hbox");
|
||||
node.MozMarginStart = (this.level * 10) + "px";
|
||||
node.appendChild(arrowNode);
|
||||
node.appendChild(document.createTextNode(this.itemDataSrc.label));
|
||||
return node;
|
||||
},
|
||||
_populateSelf: function(children) {
|
||||
for (let childDataSrc of this.itemDataSrc.children) {
|
||||
children.push(new MyCustomTreeItem(childDataSrc, {
|
||||
parent: this,
|
||||
level: this.level + 1
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let gDataSrc = {
|
||||
label: "root",
|
||||
children: [{
|
||||
label: "foo",
|
||||
children: []
|
||||
}, {
|
||||
label: "bar",
|
||||
children: [{
|
||||
label: "baz",
|
||||
children: []
|
||||
}]
|
||||
}]
|
||||
};
|
@ -0,0 +1,178 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the abstract tree base class for the profiler's tree view
|
||||
* has a functional public API.
|
||||
*/
|
||||
|
||||
let { AbstractTreeItem } = Cu.import("resource:///modules/devtools/AbstractTreeItem.jsm", {});
|
||||
let { Heritage } = Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let container = document.createElement("vbox");
|
||||
gBrowser.selectedBrowser.parentNode.appendChild(container);
|
||||
|
||||
// Populate the tree and test `expand`, `collapse` and `getChild`...
|
||||
|
||||
let treeRoot = new MyCustomTreeItem(gDataSrc, { parent: null });
|
||||
treeRoot.attachTo(container);
|
||||
|
||||
ok(!treeRoot.expanded,
|
||||
"The root node should not be expanded yet.");
|
||||
ok(!treeRoot.populated,
|
||||
"The root node should not be populated yet.");
|
||||
|
||||
treeRoot.expand();
|
||||
ok(treeRoot.expanded,
|
||||
"The root node should now be expanded.");
|
||||
ok(treeRoot.populated,
|
||||
"The root node should now be populated.");
|
||||
|
||||
let fooItem = treeRoot.getChild(0);
|
||||
let barItem = treeRoot.getChild(1);
|
||||
ok(!fooItem.expanded && !barItem.expanded,
|
||||
"The 'foo' and 'bar' nodes should not be expanded yet.");
|
||||
ok(!fooItem.populated && !barItem.populated,
|
||||
"The 'foo' and 'bar' nodes should not be populated yet.");
|
||||
|
||||
fooItem.expand();
|
||||
barItem.expand();
|
||||
ok(fooItem.expanded && barItem.expanded,
|
||||
"The 'foo' and 'bar' nodes should now be expanded.");
|
||||
ok(!fooItem.populated,
|
||||
"The 'foo' node should not be populated because it's empty.");
|
||||
ok(barItem.populated,
|
||||
"The 'bar' node should now be populated.");
|
||||
|
||||
let bazItem = barItem.getChild(0);
|
||||
ok(!bazItem.expanded,
|
||||
"The 'bar' node should not be expanded yet.");
|
||||
ok(!bazItem.populated,
|
||||
"The 'bar' node should not be populated yet.");
|
||||
|
||||
bazItem.expand();
|
||||
ok(bazItem.expanded,
|
||||
"The 'baz' node should now be expanded.");
|
||||
ok(!bazItem.populated,
|
||||
"The 'baz' node should not be populated because it's empty.");
|
||||
|
||||
ok(!treeRoot.getChild(-1) && !treeRoot.getChild(2),
|
||||
"Calling `getChild` with out of bounds indices will return null (1).");
|
||||
ok(!fooItem.getChild(-1) && !fooItem.getChild(0),
|
||||
"Calling `getChild` with out of bounds indices will return null (2).");
|
||||
ok(!barItem.getChild(-1) && !barItem.getChild(1),
|
||||
"Calling `getChild` with out of bounds indices will return null (3).");
|
||||
ok(!bazItem.getChild(-1) && !bazItem.getChild(0),
|
||||
"Calling `getChild` with out of bounds indices will return null (4).");
|
||||
|
||||
// Finished expanding all nodes in the tree...
|
||||
// Continue checking.
|
||||
|
||||
is(container.childNodes.length, 4,
|
||||
"The container node should now have four children available.");
|
||||
is(container.childNodes[0], treeRoot.target,
|
||||
"The root node's target is a child of the container node.");
|
||||
is(container.childNodes[1], fooItem.target,
|
||||
"The 'foo' node's target is a child of the container node.");
|
||||
is(container.childNodes[2], barItem.target,
|
||||
"The 'bar' node's target is a child of the container node.");
|
||||
is(container.childNodes[3], bazItem.target,
|
||||
"The 'baz' node's target is a child of the container node.");
|
||||
|
||||
treeRoot.collapse();
|
||||
is(container.childNodes.length, 1,
|
||||
"The container node should now have one children available.");
|
||||
|
||||
ok(!treeRoot.expanded,
|
||||
"The root node should not be expanded anymore.");
|
||||
ok(fooItem.expanded && barItem.expanded && bazItem.expanded,
|
||||
"The 'foo', 'bar' and 'baz' nodes should still be expanded.");
|
||||
ok(treeRoot.populated && barItem.populated,
|
||||
"The root and 'bar' nodes should still be populated.");
|
||||
ok(!fooItem.populated && !bazItem.populated,
|
||||
"The 'foo' and 'baz' nodes should still not be populated because they're empty.");
|
||||
|
||||
treeRoot.expand();
|
||||
is(container.childNodes.length, 4,
|
||||
"The container node should now have four children available again.");
|
||||
|
||||
ok(treeRoot.expanded && fooItem.expanded && barItem.expanded && bazItem.expanded,
|
||||
"The root, 'foo', 'bar' and 'baz' nodes should now be reexpanded.");
|
||||
ok(treeRoot.populated && barItem.populated,
|
||||
"The root and 'bar' nodes should still be populated.");
|
||||
ok(!fooItem.populated && !bazItem.populated,
|
||||
"The 'foo' and 'baz' nodes should still not be populated because they're empty.");
|
||||
|
||||
// Test `focus` on the root node...
|
||||
|
||||
treeRoot.focus();
|
||||
is(document.commandDispatcher.focusedElement, treeRoot.target,
|
||||
"The root node is now focused.");
|
||||
|
||||
// Test `focus` on a leaf node...
|
||||
|
||||
bazItem.focus();
|
||||
is(document.commandDispatcher.focusedElement, bazItem.target,
|
||||
"The 'baz' node is now focused.");
|
||||
|
||||
// Test `remove`...
|
||||
|
||||
barItem.remove();
|
||||
is(container.childNodes.length, 2,
|
||||
"The container node should now have two children available.");
|
||||
is(container.childNodes[0], treeRoot.target,
|
||||
"The root node should be the first in the container node.");
|
||||
is(container.childNodes[1], fooItem.target,
|
||||
"The 'foo' node should be the second in the container node.");
|
||||
|
||||
fooItem.remove();
|
||||
is(container.childNodes.length, 1,
|
||||
"The container node should now have one children available.");
|
||||
is(container.childNodes[0], treeRoot.target,
|
||||
"The root node should be the only in the container node.");
|
||||
|
||||
treeRoot.remove();
|
||||
is(container.childNodes.length, 0,
|
||||
"The container node should now have no children available.");
|
||||
|
||||
container.remove();
|
||||
finish();
|
||||
});
|
||||
|
||||
function MyCustomTreeItem(dataSrc, properties) {
|
||||
AbstractTreeItem.call(this, properties);
|
||||
this.itemDataSrc = dataSrc;
|
||||
}
|
||||
|
||||
MyCustomTreeItem.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
_displaySelf: function(document, arrowNode) {
|
||||
let node = document.createElement("hbox");
|
||||
node.MozMarginStart = (this.level * 10) + "px";
|
||||
node.appendChild(arrowNode);
|
||||
node.appendChild(document.createTextNode(this.itemDataSrc.label));
|
||||
return node;
|
||||
},
|
||||
_populateSelf: function(children) {
|
||||
for (let childDataSrc of this.itemDataSrc.children) {
|
||||
children.push(new MyCustomTreeItem(childDataSrc, {
|
||||
parent: this,
|
||||
level: this.level + 1
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let gDataSrc = {
|
||||
label: "root",
|
||||
children: [{
|
||||
label: "foo",
|
||||
children: []
|
||||
}, {
|
||||
label: "bar",
|
||||
children: [{
|
||||
label: "baz",
|
||||
children: []
|
||||
}]
|
||||
}]
|
||||
};
|
@ -0,0 +1,186 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the abstract tree base class for the profiler's tree view
|
||||
* is keyboard accessible.
|
||||
*/
|
||||
|
||||
let { AbstractTreeItem } = Cu.import("resource:///modules/devtools/AbstractTreeItem.jsm", {});
|
||||
let { Heritage } = Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let container = document.createElement("vbox");
|
||||
gBrowser.selectedBrowser.parentNode.appendChild(container);
|
||||
|
||||
// Populate the tree by pressing RIGHT...
|
||||
|
||||
let treeRoot = new MyCustomTreeItem(gDataSrc, { parent: null });
|
||||
treeRoot.attachTo(container);
|
||||
treeRoot.focus();
|
||||
|
||||
EventUtils.sendKey("RIGHT");
|
||||
ok(treeRoot.expanded,
|
||||
"The root node is now expanded.");
|
||||
is(document.commandDispatcher.focusedElement, treeRoot.target,
|
||||
"The root node is still focused.");
|
||||
|
||||
let fooItem = treeRoot.getChild(0);
|
||||
let barItem = treeRoot.getChild(1);
|
||||
|
||||
EventUtils.sendKey("RIGHT");
|
||||
ok(!fooItem.expanded,
|
||||
"The 'foo' node is not expanded yet.");
|
||||
is(document.commandDispatcher.focusedElement, fooItem.target,
|
||||
"The 'foo' node is now focused.");
|
||||
|
||||
EventUtils.sendKey("RIGHT");
|
||||
ok(fooItem.expanded,
|
||||
"The 'foo' node is now expanded.");
|
||||
is(document.commandDispatcher.focusedElement, fooItem.target,
|
||||
"The 'foo' node is still focused.");
|
||||
|
||||
EventUtils.sendKey("RIGHT");
|
||||
ok(!barItem.expanded,
|
||||
"The 'bar' node is not expanded yet.");
|
||||
is(document.commandDispatcher.focusedElement, barItem.target,
|
||||
"The 'bar' node is now focused.");
|
||||
|
||||
EventUtils.sendKey("RIGHT");
|
||||
ok(barItem.expanded,
|
||||
"The 'bar' node is now expanded.");
|
||||
is(document.commandDispatcher.focusedElement, barItem.target,
|
||||
"The 'bar' node is still focused.");
|
||||
|
||||
let bazItem = barItem.getChild(0);
|
||||
|
||||
EventUtils.sendKey("RIGHT");
|
||||
ok(!bazItem.expanded,
|
||||
"The 'baz' node is not expanded yet.");
|
||||
is(document.commandDispatcher.focusedElement, bazItem.target,
|
||||
"The 'baz' node is now focused.");
|
||||
|
||||
EventUtils.sendKey("RIGHT");
|
||||
ok(bazItem.expanded,
|
||||
"The 'baz' node is now expanded.");
|
||||
is(document.commandDispatcher.focusedElement, bazItem.target,
|
||||
"The 'baz' node is still focused.");
|
||||
|
||||
// Test RIGHT on a leaf node.
|
||||
|
||||
EventUtils.sendKey("RIGHT");
|
||||
is(document.commandDispatcher.focusedElement, bazItem.target,
|
||||
"The 'baz' node is still focused.");
|
||||
|
||||
// Test DOWN on a leaf node.
|
||||
|
||||
EventUtils.sendKey("DOWN");
|
||||
is(document.commandDispatcher.focusedElement, bazItem.target,
|
||||
"The 'baz' node is now refocused.");
|
||||
|
||||
// Test UP.
|
||||
|
||||
EventUtils.sendKey("UP");
|
||||
is(document.commandDispatcher.focusedElement, barItem.target,
|
||||
"The 'bar' node is now refocused.");
|
||||
|
||||
EventUtils.sendKey("UP");
|
||||
is(document.commandDispatcher.focusedElement, fooItem.target,
|
||||
"The 'foo' node is now refocused.");
|
||||
|
||||
EventUtils.sendKey("UP");
|
||||
is(document.commandDispatcher.focusedElement, treeRoot.target,
|
||||
"The root node is now refocused.");
|
||||
|
||||
// Test DOWN.
|
||||
|
||||
EventUtils.sendKey("DOWN");
|
||||
is(document.commandDispatcher.focusedElement, fooItem.target,
|
||||
"The 'foo' node is now refocused.");
|
||||
|
||||
EventUtils.sendKey("DOWN");
|
||||
is(document.commandDispatcher.focusedElement, barItem.target,
|
||||
"The 'bar' node is now refocused.");
|
||||
|
||||
EventUtils.sendKey("DOWN");
|
||||
is(document.commandDispatcher.focusedElement, bazItem.target,
|
||||
"The 'baz' node is now refocused.");
|
||||
|
||||
// Test LEFT.
|
||||
|
||||
EventUtils.sendKey("LEFT");
|
||||
ok(barItem.expanded,
|
||||
"The 'bar' node is still expanded.");
|
||||
is(document.commandDispatcher.focusedElement, barItem.target,
|
||||
"The 'bar' node is now refocused.");
|
||||
|
||||
EventUtils.sendKey("LEFT");
|
||||
ok(!barItem.expanded,
|
||||
"The 'bar' node is not expanded anymore.");
|
||||
is(document.commandDispatcher.focusedElement, barItem.target,
|
||||
"The 'bar' node is still focused.");
|
||||
|
||||
EventUtils.sendKey("LEFT");
|
||||
ok(treeRoot.expanded,
|
||||
"The root node is still expanded.");
|
||||
is(document.commandDispatcher.focusedElement, treeRoot.target,
|
||||
"The root node is now refocused.");
|
||||
|
||||
EventUtils.sendKey("LEFT");
|
||||
ok(!treeRoot.expanded,
|
||||
"The root node is not expanded anymore.");
|
||||
is(document.commandDispatcher.focusedElement, treeRoot.target,
|
||||
"The root node is still focused.");
|
||||
|
||||
// Test LEFT on the root node.
|
||||
|
||||
EventUtils.sendKey("LEFT");
|
||||
is(document.commandDispatcher.focusedElement, treeRoot.target,
|
||||
"The root node is still focused.");
|
||||
|
||||
// Test UP on the root node.
|
||||
|
||||
EventUtils.sendKey("UP");
|
||||
is(document.commandDispatcher.focusedElement, treeRoot.target,
|
||||
"The root node is still focused.");
|
||||
|
||||
container.remove();
|
||||
finish();
|
||||
});
|
||||
|
||||
function MyCustomTreeItem(dataSrc, properties) {
|
||||
AbstractTreeItem.call(this, properties);
|
||||
this.itemDataSrc = dataSrc;
|
||||
}
|
||||
|
||||
MyCustomTreeItem.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
_displaySelf: function(document, arrowNode) {
|
||||
let node = document.createElement("hbox");
|
||||
node.MozMarginStart = (this.level * 10) + "px";
|
||||
node.appendChild(arrowNode);
|
||||
node.appendChild(document.createTextNode(this.itemDataSrc.label));
|
||||
return node;
|
||||
},
|
||||
_populateSelf: function(children) {
|
||||
for (let childDataSrc of this.itemDataSrc.children) {
|
||||
children.push(new MyCustomTreeItem(childDataSrc, {
|
||||
parent: this,
|
||||
level: this.level + 1
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let gDataSrc = {
|
||||
label: "root",
|
||||
children: [{
|
||||
label: "foo",
|
||||
children: []
|
||||
}, {
|
||||
label: "bar",
|
||||
children: [{
|
||||
label: "baz",
|
||||
children: []
|
||||
}]
|
||||
}]
|
||||
};
|
@ -0,0 +1,166 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Verifies if FrameNodes retain and parse their data appropriately.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
let { FrameNode } = devtools.require("devtools/profiler/tree-model");
|
||||
|
||||
let frame1 = new FrameNode({
|
||||
location: "hello/<.world (http://foo/bar.js:123)",
|
||||
line: 456
|
||||
});
|
||||
|
||||
is(frame1.getInfo().nodeType, "Frame",
|
||||
"The first frame node has the correct type.");
|
||||
is(frame1.getInfo().functionName, "hello/<.world",
|
||||
"The first frame node has the correct function name.");
|
||||
is(frame1.getInfo().fileName, "bar.js",
|
||||
"The first frame node has the correct file name.");
|
||||
is(frame1.getInfo().hostName, "foo",
|
||||
"The first frame node has the correct host name.");
|
||||
is(frame1.getInfo().url, "http://foo/bar.js",
|
||||
"The first frame node has the correct url.");
|
||||
is(frame1.getInfo().line, 123,
|
||||
"The first frame node has the correct line.");
|
||||
is(frame1.getInfo().categoryData.toSource(), "({})",
|
||||
"The first frame node has the correct category data.");
|
||||
is(frame1.getInfo().isContent, true,
|
||||
"The first frame node has the correct content flag.");
|
||||
|
||||
let frame2 = new FrameNode({
|
||||
location: "hello/<.world (http://foo/bar.js#baz:123)",
|
||||
line: 456
|
||||
});
|
||||
|
||||
is(frame2.getInfo().nodeType, "Frame",
|
||||
"The second frame node has the correct type.");
|
||||
is(frame2.getInfo().functionName, "hello/<.world",
|
||||
"The second frame node has the correct function name.");
|
||||
is(frame2.getInfo().fileName, "bar.js#baz",
|
||||
"The second frame node has the correct file name.");
|
||||
is(frame2.getInfo().hostName, "foo",
|
||||
"The second frame node has the correct host name.");
|
||||
is(frame2.getInfo().url, "http://foo/bar.js#baz",
|
||||
"The second frame node has the correct url.");
|
||||
is(frame2.getInfo().line, 123,
|
||||
"The second frame node has the correct line.");
|
||||
is(frame2.getInfo().categoryData.toSource(), "({})",
|
||||
"The second frame node has the correct category data.");
|
||||
is(frame2.getInfo().isContent, true,
|
||||
"The second frame node has the correct content flag.");
|
||||
|
||||
let frame3 = new FrameNode({
|
||||
location: "hello/<.world (http://foo/#bar:123)",
|
||||
line: 456
|
||||
});
|
||||
|
||||
is(frame3.getInfo().nodeType, "Frame",
|
||||
"The third frame node has the correct type.");
|
||||
is(frame3.getInfo().functionName, "hello/<.world",
|
||||
"The third frame node has the correct function name.");
|
||||
is(frame3.getInfo().fileName, "#bar",
|
||||
"The third frame node has the correct file name.");
|
||||
is(frame3.getInfo().hostName, "foo",
|
||||
"The third frame node has the correct host name.");
|
||||
is(frame3.getInfo().url, "http://foo/#bar",
|
||||
"The third frame node has the correct url.");
|
||||
is(frame3.getInfo().line, 123,
|
||||
"The third frame node has the correct line.");
|
||||
is(frame3.getInfo().categoryData.toSource(), "({})",
|
||||
"The third frame node has the correct category data.");
|
||||
is(frame3.getInfo().isContent, true,
|
||||
"The third frame node has the correct content flag.");
|
||||
|
||||
let frame4 = new FrameNode({
|
||||
location: "hello/<.world (http://foo/:123)",
|
||||
line: 456
|
||||
});
|
||||
|
||||
is(frame4.getInfo().nodeType, "Frame",
|
||||
"The fourth frame node has the correct type.");
|
||||
is(frame4.getInfo().functionName, "hello/<.world",
|
||||
"The fourth frame node has the correct function name.");
|
||||
is(frame4.getInfo().fileName, "/",
|
||||
"The fourth frame node has the correct file name.");
|
||||
is(frame4.getInfo().hostName, "foo",
|
||||
"The fourth frame node has the correct host name.");
|
||||
is(frame4.getInfo().url, "http://foo/",
|
||||
"The fourth frame node has the correct url.");
|
||||
is(frame4.getInfo().line, 123,
|
||||
"The fourth frame node has the correct line.");
|
||||
is(frame4.getInfo().categoryData.toSource(), "({})",
|
||||
"The fourth frame node has the correct category data.");
|
||||
is(frame4.getInfo().isContent, true,
|
||||
"The fourth frame node has the correct content flag.");
|
||||
|
||||
let frame5 = new FrameNode({
|
||||
location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123)",
|
||||
line: 456
|
||||
});
|
||||
|
||||
is(frame5.getInfo().nodeType, "Frame",
|
||||
"The fifth frame node has the correct type.");
|
||||
is(frame5.getInfo().functionName, "hello/<.world",
|
||||
"The fifth frame node has the correct function name.");
|
||||
is(frame5.getInfo().fileName, "baz.js",
|
||||
"The fifth frame node has the correct file name.");
|
||||
is(frame5.getInfo().hostName, "bar",
|
||||
"The fifth frame node has the correct host name.");
|
||||
is(frame5.getInfo().url, "http://bar/baz.js",
|
||||
"The fifth frame node has the correct url.");
|
||||
is(frame5.getInfo().line, 123,
|
||||
"The fifth frame node has the correct line.");
|
||||
is(frame5.getInfo().categoryData.toSource(), "({})",
|
||||
"The fifth frame node has the correct category data.");
|
||||
is(frame5.getInfo().isContent, false,
|
||||
"The fifth frame node has the correct content flag.");
|
||||
|
||||
let frame6 = new FrameNode({
|
||||
location: "Foo::Bar::Baz",
|
||||
line: 456,
|
||||
category: 8
|
||||
});
|
||||
|
||||
is(frame6.getInfo().nodeType, "Frame",
|
||||
"The sixth frame node has the correct type.");
|
||||
is(frame6.getInfo().functionName, "Foo::Bar::Baz",
|
||||
"The sixth frame node has the correct function name.");
|
||||
is(frame6.getInfo().fileName, null,
|
||||
"The sixth frame node has the correct file name.");
|
||||
is(frame6.getInfo().hostName, null,
|
||||
"The sixth frame node has the correct host name.");
|
||||
is(frame6.getInfo().url, null,
|
||||
"The sixth frame node has the correct url.");
|
||||
is(frame6.getInfo().line, 456,
|
||||
"The sixth frame node has the correct line.");
|
||||
is(frame6.getInfo().categoryData.abbrev, "other",
|
||||
"The sixth frame node has the correct category data.");
|
||||
is(frame6.getInfo().isContent, false,
|
||||
"The sixth frame node has the correct content flag.");
|
||||
|
||||
let frame7 = new FrameNode({
|
||||
location: "EnterJIT"
|
||||
});
|
||||
|
||||
is(frame7.getInfo().nodeType, "Frame",
|
||||
"The seventh frame node has the correct type.");
|
||||
is(frame7.getInfo().functionName, "EnterJIT",
|
||||
"The seventh frame node has the correct function name.");
|
||||
is(frame7.getInfo().fileName, null,
|
||||
"The seventh frame node has the correct file name.");
|
||||
is(frame7.getInfo().hostName, null,
|
||||
"The seventh frame node has the correct host name.");
|
||||
is(frame7.getInfo().url, null,
|
||||
"The seventh frame node has the correct url.");
|
||||
is(frame7.getInfo().line, null,
|
||||
"The seventh frame node has the correct line.");
|
||||
is(frame7.getInfo().categoryData.abbrev, "js",
|
||||
"The seventh frame node has the correct category data.");
|
||||
is(frame7.getInfo().isContent, false,
|
||||
"The seventh frame node has the correct content flag.");
|
||||
|
||||
finish();
|
||||
}
|
196
browser/devtools/profiler/test/browser_profiler_tree-model-01.js
Normal file
196
browser/devtools/profiler/test/browser_profiler_tree-model-01.js
Normal file
@ -0,0 +1,196 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if a call tree model can be correctly computed from a samples array.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/profiler/tree-model");
|
||||
|
||||
// Create a root node from a given samples array.
|
||||
|
||||
let root = new ThreadNode(gSamples);
|
||||
|
||||
// Test the root node.
|
||||
|
||||
is(root.duration, 18,
|
||||
"The correct duration was calculated for the root node.");
|
||||
is(root.getInfo().nodeType, "Thread",
|
||||
"The correct node type was retrieved for the root node.");
|
||||
is(root.getInfo().functionName, "(root)",
|
||||
"The correct function name was retrieved for the root node.");
|
||||
is(root.getInfo().categoryData.toSource(), "({})",
|
||||
"The correct empty category data was retrieved for the root node.");
|
||||
|
||||
is(Object.keys(root.calls).length, 1,
|
||||
"The correct number of child calls were calculated for the root node.");
|
||||
is(Object.keys(root.calls)[0], "A",
|
||||
"The root node's only child call is correct.");
|
||||
|
||||
// Test all the descendant nodes.
|
||||
|
||||
is(Object.keys(root.calls.A.calls).length, 2,
|
||||
"The correct number of child calls were calculated for the '.A' node.");
|
||||
is(Object.keys(root.calls.A.calls)[0], "B",
|
||||
"The '.A' node's first child call is correct.");
|
||||
is(Object.keys(root.calls.A.calls)[1], "E",
|
||||
"The '.A' node's second child call is correct.");
|
||||
|
||||
is(Object.keys(root.calls.A.calls.B.calls).length, 2,
|
||||
"The correct number of child calls were calculated for the '.A.B' node.");
|
||||
is(Object.keys(root.calls.A.calls.B.calls)[0], "C",
|
||||
"The '.A.B' node's first child call is correct.");
|
||||
is(Object.keys(root.calls.A.calls.B.calls)[1], "D",
|
||||
"The '.A.B' node's second child call is correct.");
|
||||
|
||||
is(Object.keys(root.calls.A.calls.E.calls).length, 1,
|
||||
"The correct number of child calls were calculated for the '.A.E' node.");
|
||||
is(Object.keys(root.calls.A.calls.E.calls)[0], "F",
|
||||
"The '.A.E' node's only child call is correct.");
|
||||
|
||||
is(Object.keys(root.calls.A.calls.B.calls.C.calls).length, 0,
|
||||
"The correct number of child calls were calculated for the '.A.B.C' node.");
|
||||
is(Object.keys(root.calls.A.calls.B.calls.D.calls).length, 0,
|
||||
"The correct number of child calls were calculated for the '.A.B.D' node.");
|
||||
is(Object.keys(root.calls.A.calls.E.calls.F.calls).length, 0,
|
||||
"The correct number of child calls were calculated for the '.A.E.F' node.");
|
||||
|
||||
// Insert new nodes in the tree.
|
||||
|
||||
root.insert({
|
||||
time: 20,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "B" },
|
||||
{ location: "C" },
|
||||
{ location: "D" },
|
||||
{ location: "E" },
|
||||
{ location: "F" },
|
||||
{ location: "G" }
|
||||
]
|
||||
});
|
||||
|
||||
// Retest the root node.
|
||||
|
||||
is(root.duration, 20,
|
||||
"The correct duration was recalculated for the root node.");
|
||||
|
||||
is(Object.keys(root.calls).length, 1,
|
||||
"The correct number of child calls were calculated for the root node.");
|
||||
is(Object.keys(root.calls)[0], "A",
|
||||
"The root node's only child call is correct.");
|
||||
|
||||
// Retest all the descendant nodes.
|
||||
|
||||
is(Object.keys(root.calls.A.calls).length, 2,
|
||||
"The correct number of child calls were calculated for the '.A' node.");
|
||||
is(Object.keys(root.calls.A.calls)[0], "B",
|
||||
"The '.A' node's first child call is correct.");
|
||||
is(Object.keys(root.calls.A.calls)[1], "E",
|
||||
"The '.A' node's second child call is correct.");
|
||||
|
||||
is(Object.keys(root.calls.A.calls.B.calls).length, 2,
|
||||
"The correct number of child calls were calculated for the '.A.B' node.");
|
||||
is(Object.keys(root.calls.A.calls.B.calls)[0], "C",
|
||||
"The '.A.B' node's first child call is correct.");
|
||||
is(Object.keys(root.calls.A.calls.B.calls)[1], "D",
|
||||
"The '.A.B' node's second child call is correct.");
|
||||
|
||||
is(Object.keys(root.calls.A.calls.E.calls).length, 1,
|
||||
"The correct number of child calls were calculated for the '.A.E' node.");
|
||||
is(Object.keys(root.calls.A.calls.E.calls)[0], "F",
|
||||
"The '.A.E' node's only child call is correct.");
|
||||
|
||||
is(Object.keys(root.calls.A.calls.B.calls.C.calls).length, 1,
|
||||
"The correct number of child calls were calculated for the '.A.B.C' node.");
|
||||
is(Object.keys(root.calls.A.calls.B.calls.C.calls)[0], "D",
|
||||
"The '.A.B.C' node's only child call is correct.");
|
||||
|
||||
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls).length, 1,
|
||||
"The correct number of child calls were calculated for the '.A.B.C.D' node.");
|
||||
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls)[0], "E",
|
||||
"The '.A.B.C.D' node's only child call is correct.");
|
||||
|
||||
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls).length, 1,
|
||||
"The correct number of child calls were calculated for the '.A.B.C.D.E' node.");
|
||||
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls)[0], "F",
|
||||
"The '.A.B.C.D.E' node's only child call is correct.");
|
||||
|
||||
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls).length, 1,
|
||||
"The correct number of child calls were calculated for the '.A.B.C.D.E.F' node.");
|
||||
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls)[0], "G",
|
||||
"The '.A.B.C.D.E.F' node's only child call is correct.");
|
||||
|
||||
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.calls).length, 0,
|
||||
"The correct number of child calls were calculated for the '.A.B.D.E.F.G' node.");
|
||||
is(Object.keys(root.calls.A.calls.B.calls.D.calls).length, 0,
|
||||
"The correct number of child calls were calculated for the '.A.B.D' node.");
|
||||
is(Object.keys(root.calls.A.calls.E.calls.F.calls).length, 0,
|
||||
"The correct number of child calls were calculated for the '.A.E.F' node.");
|
||||
|
||||
// Check the location, sample times, duration and invocations of the root.
|
||||
|
||||
is(root.calls.A.location, "A",
|
||||
"The '.A' node has the correct location.");
|
||||
is(root.calls.A.sampleTimes.toSource(),
|
||||
"[{start:5, end:10}, {start:11, end:17}, {start:18, end:25}, {start:20, end:22}]",
|
||||
"The '.A' node has the correct sample times.");
|
||||
is(root.calls.A.duration, 20,
|
||||
"The '.A' node has the correct duration in milliseconds.");
|
||||
is(root.calls.A.invocations, 4,
|
||||
"The '.A' node has the correct number of invocations.");
|
||||
|
||||
// ...and the rightmost leaf.
|
||||
|
||||
is(root.calls.A.calls.E.calls.F.location, "F",
|
||||
"The '.A.E.F' node has the correct location.");
|
||||
is(root.calls.A.calls.E.calls.F.sampleTimes.toSource(),
|
||||
"[{start:18, end:25}]",
|
||||
"The '.A.E.F' node has the correct sample times.");
|
||||
is(root.calls.A.calls.E.calls.F.duration, 7,
|
||||
"The '.A.E.F' node has the correct duration in milliseconds.");
|
||||
is(root.calls.A.calls.E.calls.F.invocations, 1,
|
||||
"The '.A.E.F' node has the correct number of invocations.");
|
||||
|
||||
// ...and the leftmost leaf.
|
||||
|
||||
is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.location, "G",
|
||||
"The '.A.B.C.D.E.F.G' node has the correct location.");
|
||||
is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.sampleTimes.toSource(),
|
||||
"[{start:20, end:22}]",
|
||||
"The '.A.B.C.D.E.F.G' node has the correct sample times.");
|
||||
is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.duration, 2,
|
||||
"The '.A.B.C.D.E.F.G' node has the correct duration in milliseconds.");
|
||||
is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.invocations, 1,
|
||||
"The '.A.B.C.D.E.F.G' node has the correct number of invocations.");
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
let gSamples = [{
|
||||
time: 5,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "B" },
|
||||
{ location: "C" }
|
||||
]
|
||||
}, {
|
||||
time: 5 + 6,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "B" },
|
||||
{ location: "D" }
|
||||
]
|
||||
}, {
|
||||
time: 5 + 6 + 7,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "E" },
|
||||
{ location: "F" }
|
||||
]
|
||||
}];
|
@ -0,0 +1,59 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if a call tree model ignores samples with no timing information.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/profiler/tree-model");
|
||||
|
||||
// Create a root node from a given samples array.
|
||||
|
||||
let root = new ThreadNode(gSamples);
|
||||
|
||||
// Test the root node.
|
||||
|
||||
is(root.duration, 5,
|
||||
"The correct duration was calculated for the root node.");
|
||||
|
||||
is(Object.keys(root.calls).length, 1,
|
||||
"The correct number of child calls were calculated for the root node.");
|
||||
is(Object.keys(root.calls)[0], "A",
|
||||
"The root node's only child call is correct.");
|
||||
|
||||
// Test all the descendant nodes.
|
||||
|
||||
is(Object.keys(root.calls.A.calls).length, 1,
|
||||
"The correct number of child calls were calculated for the '.A' node.");
|
||||
is(Object.keys(root.calls.A.calls)[0], "B",
|
||||
"The '.A.B' node's only child call is correct.");
|
||||
|
||||
is(Object.keys(root.calls.A.calls.B.calls).length, 1,
|
||||
"The correct number of child calls were calculated for the '.A.B' node.");
|
||||
is(Object.keys(root.calls.A.calls.B.calls)[0], "C",
|
||||
"The '.A.B' node's only child call is correct.");
|
||||
|
||||
is(Object.keys(root.calls.A.calls.B.calls.C.calls).length, 0,
|
||||
"The correct number of child calls were calculated for the '.A.B.C' node.");
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
let gSamples = [{
|
||||
time: 5,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "B" },
|
||||
{ location: "C" }
|
||||
]
|
||||
}, {
|
||||
time: null,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "A" },
|
||||
{ location: "B" },
|
||||
{ location: "D" }
|
||||
]
|
||||
}];
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user