CLOSED TREE
This commit is contained in:
Ryan VanderMeulen 2014-08-06 16:50:19 -04:00
Родитель 5b16c86f37 e39aaf3dc5
Коммит b59183f1cc
193 изменённых файлов: 9788 добавлений и 9244 удалений

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

@ -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>

До

Ширина:  |  Высота:  |  Размер: 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>

До

Ширина:  |  Высота:  |  Размер: 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>

До

Ширина:  |  Высота:  |  Размер: 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;
},
};

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -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);
}
},
};

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -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;

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

@ -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),
"&lt;script&gt;function f() {}&lt;/script&gt;&lt;/textarea&gt;&lt;img/src='about:logo'&gt;");
is(win.escapeHTML(expl2),
"&lt;script&gt;function f() {}&lt;/script&gt;&lt;/pre&gt;&lt;img/src='about:logo'&gt;");
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();
}

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

@ -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" }
]
}];

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше