Bug 879008 - Remove the old Profiler frontend, r=rcampbell

This commit is contained in:
Victor Porof 2014-08-06 11:25:17 -04:00
parent 75c2d1d5a9
commit b5f375d725
52 changed files with 9 additions and 9037 deletions

View File

@ -897,21 +897,6 @@ let gDevToolsBrowser = {
* Connects to the SPS profiler when the developer tools are open.
*/
_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;
}
}
}
},
/**

View File

@ -78,21 +78,6 @@ 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/responsivedesign/resize-commands.js (responsivedesign/resize-commands.js)
content/browser/devtools/commandline.css (commandline/commandline.css)
content/browser/devtools/commandlineoutput.xhtml (commandline/commandlineoutput.xhtml)

View File

@ -264,7 +264,7 @@ 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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,27 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="64" height="16" viewBox="0 0 64 16">
<defs>
<mask id="arrowInCircle" maskContentUnits="userSpaceOnUse">
<circle cx="8" cy="8" r="6" fill="white"/>
<rect x="4.5" y="7" width="3.5" height="2" fill="black"/>
<polyline points="8 4 12 8 8 12" fill="black"/>
</mask>
</defs>
<g fill="#888">
<rect x="0" y="0" width="16" height="16" mask="url(#arrowInCircle)"/>
</g>
<g fill="#444" transform="translate(16,0)">
<rect x="0" y="0" width="16" height="16" mask="url(#arrowInCircle)"/>
</g>
<g fill="#FFF" transform="translate(32,0)">
<rect x="0" y="0" width="16" height="16" mask="url(#arrowInCircle)"/>
</g>
<g fill="rgba(255, 255, 255, 0.7)" transform="translate(48,0)">
<rect x="0" y="0" width="16" height="16" mask="url(#arrowInCircle)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,23 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="24" height="24" viewBox="0 0 64 64">
<g>
<rect x="30" y="4" width="4" height="15" transform="rotate(0, 32, 32)" fill="#BBB"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(30, 32, 32)" fill="#AAA"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(60, 32, 32)" fill="#999"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(90, 32, 32)" fill="#888"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(120, 32, 32)" fill="#777"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(150, 32, 32)" fill="#666"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(180, 32, 32)" fill="#555"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(210, 32, 32)" fill="#444"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(240, 32, 32)" fill="#333"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(270, 32, 32)" fill="#222"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(300, 32, 32)" fill="#111"/>
<rect x="30" y="4" width="4" height="15" transform="rotate(330, 32, 32)" fill="#000"/>
<animateTransform attributeName="transform" type="rotate" calcMode="discrete" values="0 32 32;30 32 32;60 32 32;90 32 32;120 32 32;150 32 32;180 32 32;210 32 32;240 32 32;270 32 32;300 32 32;330 32 32" dur="0.8s" repeatCount="indefinite"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,32 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="64" height="32" viewBox="0 0 64 32">
<g fill="#888">
<polyline points="3 4 12 4 7.5 12"/>
<g transform="translate(0,16)">
<polyline points="3 4 12 4 7.5 12" transform="rotate(-90, 7.5, 7.5)"/>
</g>
</g>
<g fill="#444" transform="translate(16,0)">
<polyline points="3 4 12 4 7.5 12"/>
<g transform="translate(0,16)">
<polyline points="3 4 12 4 7.5 12" transform="rotate(-90, 7.5, 7.5)"/>
</g>
</g>
<g fill="#FFF" transform="translate(32,0)">
<polyline points="3 4 12 4 7.5 12"/>
<g transform="translate(0,16)">
<polyline points="3 4 12 4 7.5 12" transform="rotate(-90, 7.5, 7.5)"/>
</g>
</g>
<g fill="rgba(255, 255, 255, 0.7)" transform="translate(48,0)">
<polyline points="3 4 12 4 7.5 12"/>
<g transform="translate(0,16)">
<polyline points="3 4 12 4 7.5 12" transform="rotate(-90, 7.5, 7.5)"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

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

View File

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

View File

@ -1,271 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
Array.prototype.clone = function() { return this.slice(0); }
function makeSample(frames, extraInfo, lines) {
return {
frames: frames,
extraInfo: extraInfo,
lines: lines
};
}
function cloneSample(sample) {
return makeSample(sample.frames.clone(), sample.extraInfo, sample.lines.clone());
}
function bucketsBySplittingArray(array, maxItemsPerBucket) {
var buckets = [];
while (buckets.length * maxItemsPerBucket < array.length) {
buckets.push(array.slice(buckets.length * maxItemsPerBucket,
(buckets.length + 1) * maxItemsPerBucket));
}
return buckets;
}
var gParserWorker = new Worker("profiler/cleopatra/js/parserWorker.js");
gParserWorker.nextRequestID = 0;
function WorkerRequest(worker) {
var self = this;
this._eventListeners = {};
var requestID = worker.nextRequestID++;
this._requestID = requestID;
this._worker = worker;
this._totalReporter = new ProgressReporter();
this._totalReporter.addListener(function (reporter) {
self._fireEvent("progress", reporter.getProgress(), reporter.getAction());
})
this._sendChunkReporter = this._totalReporter.addSubreporter(500);
this._executeReporter = this._totalReporter.addSubreporter(3000);
this._receiveChunkReporter = this._totalReporter.addSubreporter(100);
this._totalReporter.begin("Processing task in worker...");
var partialResult = null;
function onMessageFromWorker(msg) {
pendingMessages.push(msg);
scheduleMessageProcessing();
}
function processMessage(msg) {
var startTime = Date.now();
var data = msg.data;
var readTime = Date.now() - startTime;
if (data.requestID == requestID || !data.requestID) {
switch(data.type) {
case "error":
self._sendChunkReporter.setAction("Error in worker: " + data.error);
self._executeReporter.setAction("Error in worker: " + data.error);
self._receiveChunkReporter.setAction("Error in worker: " + data.error);
self._totalReporter.setAction("Error in worker: " + data.error);
PROFILERERROR("Error in worker: " + data.error);
self._fireEvent("error", data.error);
break;
case "progress":
self._executeReporter.setProgress(data.progress);
break;
case "finished":
self._executeReporter.finish();
self._receiveChunkReporter.begin("Receiving data from worker...");
self._receiveChunkReporter.finish();
self._fireEvent("finished", data.result);
worker.removeEventListener("message", onMessageFromWorker);
break;
case "finishedStart":
partialResult = null;
self._totalReceiveChunks = data.numChunks;
self._gotReceiveChunks = 0;
self._executeReporter.finish();
self._receiveChunkReporter.begin("Receiving data from worker...");
break;
case "finishedChunk":
partialResult = partialResult ? partialResult.concat(data.chunk) : data.chunk;
var chunkIndex = self._gotReceiveChunks++;
self._receiveChunkReporter.setProgress((chunkIndex + 1) / self._totalReceiveChunks);
break;
case "finishedEnd":
self._receiveChunkReporter.finish();
self._fireEvent("finished", partialResult);
worker.removeEventListener("message", onMessageFromWorker);
break;
}
// dump log if present
if (data.log) {
for (var line in data.log) {
PROFILERLOG(line);
}
}
}
}
var pendingMessages = [];
var messageProcessingTimer = 0;
function processMessages() {
messageProcessingTimer = 0;
processMessage(pendingMessages.shift());
if (pendingMessages.length)
scheduleMessageProcessing();
}
function scheduleMessageProcessing() {
if (messageProcessingTimer)
return;
messageProcessingTimer = setTimeout(processMessages, 10);
}
worker.addEventListener("message", onMessageFromWorker);
}
WorkerRequest.prototype = {
send: function WorkerRequest_send(task, taskData) {
this._sendChunkReporter.begin("Sending data to worker...");
var startTime = Date.now();
this._worker.postMessage({
requestID: this._requestID,
task: task,
taskData: taskData
});
var postTime = Date.now() - startTime;
this._sendChunkReporter.finish();
this._executeReporter.begin("Processing worker request...");
},
sendInChunks: function WorkerRequest_sendInChunks(task, taskData, params, maxChunkSize) {
this._sendChunkReporter.begin("Sending data to worker...");
var self = this;
var chunks = bucketsBySplittingArray(taskData, maxChunkSize);
var pendingMessages = [
{
requestID: this._requestID,
task: "chunkedStart",
numChunks: chunks.length
}
].concat(chunks.map(function (chunk) {
return {
requestID: self._requestID,
task: "chunkedChunk",
chunk: chunk
};
})).concat([
{
requestID: this._requestID,
task: "chunkedEnd"
},
{
requestID: this._requestID,
params: params,
task: task
},
]);
var totalMessages = pendingMessages.length;
var numSentMessages = 0;
function postMessage(msg) {
var msgIndex = numSentMessages++;
var startTime = Date.now();
self._worker.postMessage(msg);
var postTime = Date.now() - startTime;
self._sendChunkReporter.setProgress((msgIndex + 1) / totalMessages);
}
var messagePostingTimer = 0;
function postMessages() {
messagePostingTimer = 0;
postMessage(pendingMessages.shift());
if (pendingMessages.length) {
scheduleMessagePosting();
} else {
self._sendChunkReporter.finish();
self._executeReporter.begin("Processing worker request...");
}
}
function scheduleMessagePosting() {
if (messagePostingTimer)
return;
messagePostingTimer = setTimeout(postMessages, 10);
}
scheduleMessagePosting();
},
// TODO: share code with TreeView
addEventListener: function WorkerRequest_addEventListener(eventName, callbackFunction) {
if (!(eventName in this._eventListeners))
this._eventListeners[eventName] = [];
if (this._eventListeners[eventName].indexOf(callbackFunction) != -1)
return;
this._eventListeners[eventName].push(callbackFunction);
},
removeEventListener: function WorkerRequest_removeEventListener(eventName, callbackFunction) {
if (!(eventName in this._eventListeners))
return;
var index = this._eventListeners[eventName].indexOf(callbackFunction);
if (index == -1)
return;
this._eventListeners[eventName].splice(index, 1);
},
_fireEvent: function WorkerRequest__fireEvent(eventName, eventObject, p1) {
if (!(eventName in this._eventListeners))
return;
this._eventListeners[eventName].forEach(function (callbackFunction) {
callbackFunction(eventObject, p1);
});
},
}
var Parser = {
parse: function Parser_parse(data, params) {
var request = new WorkerRequest(gParserWorker);
request.sendInChunks("parseRawProfile", data, params, 3000000);
return request;
},
updateFilters: function Parser_updateFilters(filters) {
var request = new WorkerRequest(gParserWorker);
request.send("updateFilters", {
filters: filters,
profileID: 0
});
return request;
},
updateViewOptions: function Parser_updateViewOptions(options) {
var request = new WorkerRequest(gParserWorker);
request.send("updateViewOptions", {
options: options,
profileID: 0
});
return request;
},
getSerializedProfile: function Parser_getSerializedProfile(complete, callback) {
var request = new WorkerRequest(gParserWorker);
request.send("getSerializedProfile", {
profileID: 0,
complete: complete
});
request.addEventListener("finished", callback);
},
calculateHistogramData: function Parser_calculateHistogramData() {
var request = new WorkerRequest(gParserWorker);
request.send("calculateHistogramData", {
profileID: 0
});
return request;
},
calculateDiagnosticItems: function Parser_calculateDiagnosticItems(meta) {
var request = new WorkerRequest(gParserWorker);
request.send("calculateDiagnosticItems", {
profileID: 0,
meta: meta
});
return request;
},
updateLogSetting: function Parser_updateLogSetting() {
var request = new WorkerRequest(gParserWorker);
request.send("initWorker", {
debugLog: gDebugLog,
debugTrace: gDebugTrace,
});
return request;
},
};

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,702 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var kMaxChunkDuration = 30; // ms
function escapeHTML(html) {
var pre = document.createElementNS("http://www.w3.org/1999/xhtml", "pre");
var text = document.createTextNode(html);
pre.appendChild(text);
return pre.innerHTML;
}
RegExp.escape = function(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
var requestAnimationFrame = window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback, element) {
return window.setTimeout(callback, 1000 / 60);
};
var cancelAnimationFrame = window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
window.msCancelAnimationFrame ||
function(req) {
window.clearTimeout(req);
};
function TreeView() {
this._eventListeners = {};
this._pendingActions = [];
this._pendingActionsProcessingCallback = null;
this._container = document.createElement("div");
this._container.className = "treeViewContainer";
this._container.setAttribute("tabindex", "0"); // make it focusable
this._header = document.createElement("ul");
this._header.className = "treeHeader";
this._container.appendChild(this._header);
this._verticalScrollbox = document.createElement("div");
this._verticalScrollbox.className = "treeViewVerticalScrollbox";
this._container.appendChild(this._verticalScrollbox);
this._leftColumnBackground = document.createElement("div");
this._leftColumnBackground.className = "leftColumnBackground";
this._verticalScrollbox.appendChild(this._leftColumnBackground);
this._horizontalScrollbox = document.createElement("div");
this._horizontalScrollbox.className = "treeViewHorizontalScrollbox";
this._verticalScrollbox.appendChild(this._horizontalScrollbox);
this._styleElement = document.createElement("style");
this._styleElement.setAttribute("type", "text/css");
this._container.appendChild(this._styleElement);
this._contextMenu = document.createElement("menu");
this._contextMenu.setAttribute("type", "context");
this._contextMenu.id = "contextMenuForTreeView" + TreeView.instanceCounter++;
this._container.appendChild(this._contextMenu);
this._busyCover = document.createElement("div");
this._busyCover.className = "busyCover";
this._container.appendChild(this._busyCover);
this._abortToggleAll = false;
this.initSelection = true;
var self = this;
this._container.onkeydown = function (e) {
self._onkeypress(e);
};
this._container.onkeypress = function (e) {
// on key down gives us '8' and mapping shift+8='*' may not be portable.
if (String.fromCharCode(e.charCode) == '*')
self._onkeypress(e);
};
this._container.onclick = function (e) {
self._onclick(e);
};
this._verticalScrollbox.addEventListener("contextmenu", function(event) {
self._populateContextMenu(event);
}, true);
this._setUpScrolling();
};
TreeView.instanceCounter = 0;
TreeView.prototype = {
getContainer: function TreeView_getContainer() {
return this._container;
},
setColumns: function TreeView_setColumns(columns) {
this._header.innerHTML = "";
for (var i = 0; i < columns.length; i++) {
var li = document.createElement("li");
li.className = "treeColumnHeader treeColumnHeader" + i;
li.id = columns[i].name + "Header";
li.textContent = columns[i].title;
this._header.appendChild(li);
}
},
dataIsOutdated: function TreeView_dataIsOutdated() {
this._busyCover.classList.add("busy");
},
display: function TreeView_display(data, resources, filterByName) {
this._busyCover.classList.remove("busy");
this._filterByName = filterByName;
this._resources = resources;
this._addResourceIconStyles();
this._filterByNameReg = null; // lazy init
if (this._filterByName === "")
this._filterByName = null;
this._horizontalScrollbox.innerHTML = "";
this._horizontalScrollbox.data = data[0].getData();
if (this._pendingActionsProcessingCallback) {
cancelAnimationFrame(this._pendingActionsProcessingCallback);
this._pendingActionsProcessingCallback = 0;
}
this._pendingActions = [];
this._pendingActions.push({
parentElement: this._horizontalScrollbox,
parentNode: null,
data: data[0].getData()
});
this._processPendingActionsChunk();
changeFocus(this._container);
},
// Provide a snapshot of the reverse selection to restore with 'invert callback'
getReverseSelectionSnapshot: function TreeView__getReverseSelectionSnapshot(isJavascriptOnly) {
var snapshot = [];
if (!this._selectedNode) {
return snapshot;
}
var curr = this._selectedNode.data;
while(curr) {
if (isJavascriptOnly && curr.isJSFrame || !isJavascriptOnly) {
snapshot.push(curr.name);
//dump(JSON.stringify(curr.name) + "\n");
}
if (curr.treeChildren && curr.treeChildren.length >= 1) {
curr = curr.treeChildren[0].getData();
} else {
break;
}
}
return snapshot.reverse();
},
// Provide a snapshot of the current selection to restore
getSelectionSnapshot: function TreeView__getSelectionSnapshot(isJavascriptOnly) {
var snapshot = [];
var curr = this._selectedNode;
while(curr) {
if (isJavascriptOnly && curr.data.isJSFrame || !isJavascriptOnly) {
snapshot.push(curr.data.name);
//dump(JSON.stringify(curr.data.name) + "\n");
}
curr = curr.treeParent;
}
return snapshot.reverse();
},
setSelection: function TreeView_setSelection(frames) {
this.restoreSelectionSnapshot(frames, false);
},
// Take a selection snapshot and restore the selection
restoreSelectionSnapshot: function TreeView_restoreSelectionSnapshot(snapshot, allowNonContiguous) {
var currNode = this._horizontalScrollbox.firstChild;
if (currNode.data.name == snapshot[0] || snapshot[0] == "(total)") {
snapshot.shift();
}
//dump("len: " + snapshot.length + "\n");
next_level: while (currNode && snapshot.length > 0) {
this._toggle(currNode, false, true);
this._syncProcessPendingActionProcessing();
for (var i = 0; i < currNode.treeChildren.length; i++) {
if (currNode.treeChildren[i].data.name == snapshot[0]) {
snapshot.shift();
this._toggle(currNode, false, true);
currNode = currNode.treeChildren[i];
continue next_level;
}
}
if (allowNonContiguous) {
// We need to do a Breadth-first search to find a match
var pendingSearch = [currNode.data];
while (pendingSearch.length > 0) {
var node = pendingSearch.shift();
if (!node.treeChildren)
continue;
for (var i = 0; i < node.treeChildren.length; i++) {
var childNode = node.treeChildren[i].getData();
if (childNode.name == snapshot[0]) {
//dump("found: " + childNode.name + "\n");
snapshot.shift();
var nodesToToggle = [childNode];
while (nodesToToggle[0].name != currNode.data.name) {
nodesToToggle.splice(0, 0, nodesToToggle[0].parent);
}
var lastToggle = currNode;
for (var j = 0; j < nodesToToggle.length; j++) {
for (var k = 0; k < lastToggle.treeChildren.length; k++) {
if (lastToggle.treeChildren[k].data.name == nodesToToggle[j].name) {
//dump("Expend: " + nodesToToggle[j].name + "\n");
this._toggle(lastToggle.treeChildren[k], false, true);
lastToggle = lastToggle.treeChildren[k];
this._syncProcessPendingActionProcessing();
}
}
}
currNode = lastToggle;
continue next_level;
}
//dump("pending: " + childNode.name + "\n");
pendingSearch.push(childNode);
}
}
}
break; // Didn't find child node matching
}
if (currNode == this._horizontalScrollbox) {
PROFILERERROR("Failed to restore selection, could not find root.\n");
return;
}
this._toggle(currNode, true, true);
this._select(currNode);
},
_processPendingActionsChunk: function TreeView__processPendingActionsChunk(isSync) {
this._pendingActionsProcessingCallback = 0;
var startTime = Date.now();
var endTime = startTime + kMaxChunkDuration;
while ((isSync == true || Date.now() < endTime) && this._pendingActions.length > 0) {
this._processOneAction(this._pendingActions.shift());
}
this._scrollHeightChanged();
this._schedulePendingActionProcessing();
},
_schedulePendingActionProcessing: function TreeView__schedulePendingActionProcessing() {
if (!this._pendingActionsProcessingCallback && this._pendingActions.length > 0) {
var self = this;
this._pendingActionsProcessingCallback = requestAnimationFrame(function () {
self._processPendingActionsChunk();
});
}
},
_syncProcessPendingActionProcessing: function TreeView__syncProcessPendingActionProcessing() {
this._processPendingActionsChunk(true);
},
_processOneAction: function TreeView__processOneAction(action) {
var li = this._createTree(action.parentElement, action.parentNode, action.data);
if ("allChildrenCollapsedValue" in action) {
if (this._abortToggleAll)
return;
this._toggleAll(li, action.allChildrenCollapsedValue, true);
}
},
addEventListener: function TreeView_addEventListener(eventName, callbackFunction) {
if (!(eventName in this._eventListeners))
this._eventListeners[eventName] = [];
if (this._eventListeners[eventName].indexOf(callbackFunction) != -1)
return;
this._eventListeners[eventName].push(callbackFunction);
},
removeEventListener: function TreeView_removeEventListener(eventName, callbackFunction) {
if (!(eventName in this._eventListeners))
return;
var index = this._eventListeners[eventName].indexOf(callbackFunction);
if (index == -1)
return;
this._eventListeners[eventName].splice(index, 1);
},
_fireEvent: function TreeView__fireEvent(eventName, eventObject) {
if (!(eventName in this._eventListeners))
return;
this._eventListeners[eventName].forEach(function (callbackFunction) {
callbackFunction(eventObject);
});
},
_setUpScrolling: function TreeView__setUpScrolling() {
var waitingForPaint = false;
var accumulatedDeltaX = 0;
var accumulatedDeltaY = 0;
var self = this;
function scrollListener(e) {
if (!waitingForPaint) {
requestAnimationFrame(function () {
self._horizontalScrollbox.scrollLeft += accumulatedDeltaX;
self._verticalScrollbox.scrollTop += accumulatedDeltaY;
accumulatedDeltaX = 0;
accumulatedDeltaY = 0;
waitingForPaint = false;
});
waitingForPaint = true;
}
if (e.axis == e.HORIZONTAL_AXIS) {
accumulatedDeltaX += e.detail;
} else {
accumulatedDeltaY += e.detail;
}
e.preventDefault();
}
this._verticalScrollbox.addEventListener("MozMousePixelScroll", scrollListener, false);
this._verticalScrollbox.cleanUp = function () {
self._verticalScrollbox.removeEventListener("MozMousePixelScroll", scrollListener, false);
};
},
_scrollHeightChanged: function TreeView__scrollHeightChanged() {
if (!this._pendingScrollHeightChanged) {
var self = this;
this._pendingScrollHeightChanged = setTimeout(function() {
self._leftColumnBackground.style.height = self._horizontalScrollbox.getBoundingClientRect().height + 'px';
self._pendingScrollHeightChanged = null;
}, 0);
}
},
_createTree: function TreeView__createTree(parentElement, parentNode, data) {
var div = document.createElement("div");
div.className = "treeViewNode collapsed";
var hasChildren = ("children" in data) && (data.children.length > 0);
if (!hasChildren)
div.classList.add("leaf");
var treeLine = document.createElement("div");
treeLine.className = "treeLine";
treeLine.innerHTML = this._HTMLForFunction(data);
div.depth = parentNode ? parentNode.depth + 1 : 0;
div.style.marginLeft = div.depth + "em";
// When this item is toggled we will expand its children
div.pendingExpand = [];
div.treeLine = treeLine;
div.data = data;
// Useful for debugging
//this.uniqueID = this.uniqueID || 0;
//div.id = "Node" + this.uniqueID++;
div.appendChild(treeLine);
div.treeChildren = [];
div.treeParent = parentNode;
if (hasChildren) {
for (var i = 0; i < data.children.length; ++i) {
div.pendingExpand.push({parentElement: this._horizontalScrollbox, parentNode: div, data: data.children[i].getData() });
}
}
if (parentNode) {
parentNode.treeChildren.push(div);
}
if (parentNode != null) {
var nextTo;
if (parentNode.treeChildren.length > 1) {
nextTo = parentNode.treeChildren[parentNode.treeChildren.length-2].nextSibling;
} else {
nextTo = parentNode.nextSibling;
}
parentElement.insertBefore(div, nextTo);
} else {
parentElement.appendChild(div);
}
return div;
},
_addResourceIconStyles: function TreeView__addResourceIconStyles() {
var styles = [];
for (var resourceName in this._resources) {
var resource = this._resources[resourceName];
if (resource.icon) {
styles.push('.resourceIcon[data-resource="' + resourceName + '"] { background-image: url("' + resource.icon + '"); }');
}
}
this._styleElement.textContent = styles.join("\n");
},
_populateContextMenu: function TreeView__populateContextMenu(event) {
this._verticalScrollbox.setAttribute("contextmenu", "");
var target = event.target;
if (target.classList.contains("expandCollapseButton") ||
target.classList.contains("focusCallstackButton"))
return;
var li = this._getParentTreeViewNode(target);
if (!li)
return;
this._select(li);
this._contextMenu.innerHTML = "";
var self = this;
this._contextMenuForFunction(li.data).forEach(function (menuItem) {
var menuItemNode = document.createElement("menuitem");
menuItemNode.onclick = (function (menuItem) {
return function() {
self._contextMenuClick(li.data, menuItem);
};
})(menuItem);
menuItemNode.label = menuItem;
self._contextMenu.appendChild(menuItemNode);
});
this._verticalScrollbox.setAttribute("contextmenu", this._contextMenu.id);
},
_contextMenuClick: function TreeView__contextMenuClick(node, menuItem) {
this._fireEvent("contextMenuClick", { node: node, menuItem: menuItem });
},
_contextMenuForFunction: function TreeView__contextMenuForFunction(node) {
// TODO move me outside tree.js
var menu = [];
if (node.library && (
node.library.toLowerCase() == "lib_xul" ||
node.library.toLowerCase() == "lib_xul.dll"
)) {
menu.push("View Source");
}
if (node.isJSFrame && node.scriptLocation) {
menu.push("View JS Source");
}
menu.push("Focus Frame");
menu.push("Focus Callstack");
menu.push("Google Search");
menu.push("Plugin View: Pie");
menu.push("Plugin View: Tree");
return menu;
},
_HTMLForFunction: function TreeView__HTMLForFunction(node) {
var nodeName = escapeHTML(node.name);
var resource = this._resources[node.library] || {};
var libName = escapeHTML(resource.name || "");
if (this._filterByName) {
if (!this._filterByNameReg) {
this._filterByName = RegExp.escape(this._filterByName);
this._filterByNameReg = new RegExp("(" + this._filterByName + ")","gi");
}
nodeName = nodeName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>");
libName = libName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>");
}
var samplePercentage;
if (isNaN(node.ratio)) {
samplePercentage = "";
} else {
samplePercentage = (100 * node.ratio).toFixed(1) + "%";
}
return '<input type="button" value="Expand / Collapse" class="expandCollapseButton" tabindex="-1"> ' +
'<span class="sampleCount">' + node.counter + '</span> ' +
'<span class="samplePercentage">' + samplePercentage + '</span> ' +
'<span class="selfSampleCount">' + node.selfCounter + '</span> ' +
'<span class="resourceIcon" data-resource="' + node.library + '"></span> ' +
'<span class="functionName">' + nodeName + '</span>' +
'<span class="libraryName">' + libName + '</span>' +
((nodeName === '(total)' || gHideSourceLinks) ? '' :
'<input type="button" value="Focus Callstack" title="Focus Callstack" class="focusCallstackButton" tabindex="-1">');
},
_resolveChildren: function TreeView__resolveChildren(div, childrenCollapsedValue) {
while (div.pendingExpand != null && div.pendingExpand.length > 0) {
var pendingExpand = div.pendingExpand.shift();
pendingExpand.allChildrenCollapsedValue = childrenCollapsedValue;
this._pendingActions.push(pendingExpand);
this._schedulePendingActionProcessing();
}
},
_showChild: function TreeView__showChild(div, isVisible) {
for (var i = 0; i < div.treeChildren.length; i++) {
div.treeChildren[i].style.display = isVisible?"":"none";
if (!isVisible) {
div.treeChildren[i].classList.add("collapsed");
this._showChild(div.treeChildren[i], isVisible);
}
}
},
_toggle: function TreeView__toggle(div, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
var currentCollapsedValue = this._isCollapsed(div);
if (newCollapsedValue === undefined)
newCollapsedValue = !currentCollapsedValue;
if (newCollapsedValue) {
div.classList.add("collapsed");
this._showChild(div, false);
} else {
this._resolveChildren(div, true);
div.classList.remove("collapsed");
this._showChild(div, true);
}
if (!suppressScrollHeightNotification)
this._scrollHeightChanged();
},
_toggleAll: function TreeView__toggleAll(subtreeRoot, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
// Reset abort
this._abortToggleAll = false;
// Expands / collapses all child nodes, too.
if (newCollapsedValue === undefined)
newCollapsedValue = !this._isCollapsed(subtreeRoot);
if (!newCollapsedValue) {
// expanding
this._resolveChildren(subtreeRoot, newCollapsedValue);
}
this._toggle(subtreeRoot, newCollapsedValue, true);
for (var i = 0; i < subtreeRoot.treeChildren.length; ++i) {
this._toggleAll(subtreeRoot.treeChildren[i], newCollapsedValue, true);
}
if (!suppressScrollHeightNotification)
this._scrollHeightChanged();
},
_getParent: function TreeView__getParent(div) {
return div.treeParent;
},
_getFirstChild: function TreeView__getFirstChild(div) {
if (this._isCollapsed(div))
return null;
var child = div.treeChildren[0];
return child;
},
_getLastChild: function TreeView__getLastChild(div) {
if (this._isCollapsed(div))
return div;
var lastChild = div.treeChildren[div.treeChildren.length-1];
if (lastChild == null)
return div;
return this._getLastChild(lastChild);
},
_getPrevSib: function TreeView__getPevSib(div) {
if (div.treeParent == null)
return null;
var nodeIndex = div.treeParent.treeChildren.indexOf(div);
if (nodeIndex == 0)
return null;
return div.treeParent.treeChildren[nodeIndex-1];
},
_getNextSib: function TreeView__getNextSib(div) {
if (div.treeParent == null)
return null;
var nodeIndex = div.treeParent.treeChildren.indexOf(div);
if (nodeIndex == div.treeParent.treeChildren.length - 1)
return this._getNextSib(div.treeParent);
return div.treeParent.treeChildren[nodeIndex+1];
},
_scheduleScrollIntoView: function TreeView__scheduleScrollIntoView(element, maxImportantWidth) {
// Schedule this on the animation frame otherwise we may run this more then once per frames
// causing more work then needed.
var self = this;
if (self._pendingAnimationFrame != null) {
return;
}
self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() {
cancelAnimationFrame(self._pendingAnimationFrame);
self._pendingAnimationFrame = null;
self._scrollIntoView(element, maxImportantWidth);
});
},
_scrollIntoView: function TreeView__scrollIntoView(element, maxImportantWidth) {
// Make sure that element is inside the visible part of our scrollbox by
// adjusting the scroll positions. If element is wider or
// higher than the scroll port, the left and top edges are prioritized over
// the right and bottom edges.
// If maxImportantWidth is set, parts of the beyond this widths are
// considered as not important; they'll not be moved into view.
if (maxImportantWidth === undefined)
maxImportantWidth = Infinity;
var visibleRect = {
left: this._horizontalScrollbox.getBoundingClientRect().left + 150, // TODO: un-hardcode 150
top: this._verticalScrollbox.getBoundingClientRect().top,
right: this._horizontalScrollbox.getBoundingClientRect().right,
bottom: this._verticalScrollbox.getBoundingClientRect().bottom
}
var r = element.getBoundingClientRect();
var right = Math.min(r.right, r.left + maxImportantWidth);
var leftCutoff = visibleRect.left - r.left;
var rightCutoff = right - visibleRect.right;
var topCutoff = visibleRect.top - r.top;
var bottomCutoff = r.bottom - visibleRect.bottom;
if (leftCutoff > 0)
this._horizontalScrollbox.scrollLeft -= leftCutoff;
else if (rightCutoff > 0)
this._horizontalScrollbox.scrollLeft += Math.min(rightCutoff, -leftCutoff);
if (topCutoff > 0)
this._verticalScrollbox.scrollTop -= topCutoff;
else if (bottomCutoff > 0)
this._verticalScrollbox.scrollTop += Math.min(bottomCutoff, -topCutoff);
},
_select: function TreeView__select(li) {
if (this._selectedNode != null) {
this._selectedNode.treeLine.classList.remove("selected");
this._selectedNode = null;
}
if (li) {
li.treeLine.classList.add("selected");
this._selectedNode = li;
var functionName = li.treeLine.querySelector(".functionName");
this._scheduleScrollIntoView(functionName, 400);
this._fireEvent("select", li.data);
}
updateDocumentURL();
},
_isCollapsed: function TreeView__isCollapsed(div) {
return div.classList.contains("collapsed");
},
_getParentTreeViewNode: function TreeView__getParentTreeViewNode(node) {
while (node) {
if (node.nodeType != node.ELEMENT_NODE)
break;
if (node.classList.contains("treeViewNode"))
return node;
node = node.parentNode;
}
return null;
},
_onclick: function TreeView__onclick(event) {
var target = event.target;
var node = this._getParentTreeViewNode(target);
if (!node)
return;
if (target.classList.contains("expandCollapseButton")) {
if (event.altKey)
this._toggleAll(node);
else
this._toggle(node);
} else if (target.classList.contains("focusCallstackButton")) {
this._fireEvent("focusCallstackButtonClicked", node.data);
} else {
this._select(node);
if (event.detail == 2) // dblclick
this._toggle(node);
}
},
_onkeypress: function TreeView__onkeypress(event) {
if (event.ctrlKey || event.altKey || event.metaKey)
return;
this._abortToggleAll = true;
var selected = this._selectedNode;
if (event.keyCode < 37 || event.keyCode > 40) {
if (event.keyCode != 0 ||
String.fromCharCode(event.charCode) != '*') {
return;
}
}
event.stopPropagation();
event.preventDefault();
if (!selected)
return;
if (event.keyCode == 37) { // KEY_LEFT
var isCollapsed = this._isCollapsed(selected);
if (!isCollapsed) {
this._toggle(selected);
} else {
var parent = this._getParent(selected);
if (parent != null) {
this._select(parent);
}
}
} else if (event.keyCode == 38) { // KEY_UP
var prevSib = this._getPrevSib(selected);
var parent = this._getParent(selected);
if (prevSib != null) {
this._select(this._getLastChild(prevSib));
} else if (parent != null) {
this._select(parent);
}
} else if (event.keyCode == 39) { // KEY_RIGHT
var isCollapsed = this._isCollapsed(selected);
if (isCollapsed) {
this._toggle(selected);
this._syncProcessPendingActionProcessing();
} else {
// Do KEY_DOWN
var nextSib = this._getNextSib(selected);
var child = this._getFirstChild(selected);
if (child != null) {
this._select(child);
} else if (nextSib) {
this._select(nextSib);
}
}
} else if (event.keyCode == 40) { // KEY_DOWN
var nextSib = this._getNextSib(selected);
var child = this._getFirstChild(selected);
if (child != null) {
this._select(child);
} else if (nextSib) {
this._select(nextSib);
}
} else if (String.fromCharCode(event.charCode) == '*') {
this._toggleAll(selected);
}
},
};

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -1,16 +1,4 @@
# -*- 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',
]

View File

@ -1,600 +1,6 @@
/* -*- 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 {
PROFILE_IDLE,
PROFILE_RUNNING,
PROFILE_COMPLETED,
SHOW_PLATFORM_DATA,
L10N_BUNDLE
} = require("devtools/profiler/consts");
const { TextEncoder } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
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", {});
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 = {};
EventEmitter.decorate(this);
}
ProfilerPanel.prototype = {
isReady: null,
window: null,
document: null,
target: null,
controller: null,
profiles: null,
sidebar: null,
_uid: null,
_activeUid: null,
_runningUid: null,
_browserWin: null,
_msgQueue: null,
get controls() {
let doc = this.document;
return {
get record() doc.querySelector("#profiler-start"),
get 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.
*
* @return Promise
*/
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);
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.isReady = true;
this.emit("ready");
deferred.resolve(this);
});
this.controller.on("profileEnd", (_, data) => {
this.importProfile(data.name, data.data);
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");
});
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;
this.emit("destroyed");
}
};
module.exports = ProfilerPanel;

View File

@ -1,52 +1,4 @@
<?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"?>
<!DOCTYPE window [
<!ENTITY % profilerDTD SYSTEM "chrome://browser/locale/devtools/profiler.dtd">
%profilerDTD;
]>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<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;"
class="devtools-toolbarbutton"
disabled="true"/>
<toolbarbutton id="profiler-import"
class="devtools-toolbarbutton"
disabled="true"
label="&importProfile.label;"/>
</hbox>
</toolbar>
<vbox id="profiles-list" flex="1">
</vbox>
</vbox>
<splitter class="devtools-side-splitter devtools-invisible-splitter"/>
<vbox flex="1" id="profiler-report">
<!-- Example:
<iframe id="profiler-cleo-1"
src="devtools/cleopatra.html" flex="1"></iframe>
-->
</vbox>
</box>
</window>

View File

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

View File

@ -1,24 +1,4 @@
[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 =
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]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,128 +1,3 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let temp = {};
const PROFILER_ENABLED = "devtools.profiler.enabled";
const REMOTE_ENABLED = "devtools.debugger.remote-enabled";
const SHOW_PLATFORM_DATA = "devtools.profiler.ui.show-platform-data";
const PROFILE_IDLE = 0;
const PROFILE_RUNNING = 1;
const PROFILE_COMPLETED = 2;
Cu.import("resource:///modules/devtools/gDevTools.jsm", temp);
let gDevTools = temp.gDevTools;
Cu.import("resource://gre/modules/devtools/Loader.jsm", temp);
let TargetFactory = temp.devtools.TargetFactory;
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", temp);
let DebuggerServer = temp.DebuggerServer;
// Import the GCLI test helper
let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
gDevTools.testing = true;
SimpleTest.registerCleanupFunction(() => {
gDevTools.testing = false;
});
registerCleanupFunction(function () {
helpers = null;
Services.prefs.clearUserPref(PROFILER_ENABLED);
Services.prefs.clearUserPref(REMOTE_ENABLED);
Services.prefs.clearUserPref(SHOW_PLATFORM_DATA);
DebuggerServer.destroy();
// These tests use a lot of memory due to GL contexts, so force a GC to help
// fragmentation.
info("Forcing GC after profiler test.");
Cu.forceGC();
});
function getProfileInternals(uid) {
let profile = (uid != null) ? gPanel.profiles.get(uid) : gPanel.activeProfile;
let win = profile.iframe.contentWindow;
let doc = win.document;
return [win, doc];
}
function getSidebarItem(uid, panel=gPanel) {
let profile = panel.profiles.get(uid);
return panel.sidebar.getItemByProfile(profile);
}
function sendFromProfile(uid, msg) {
let [win, doc] = getProfileInternals(uid);
win.parent.postMessage({ uid: uid, status: msg }, "*");
}
function loadTab(url, callback) {
let tab = gBrowser.addTab();
gBrowser.selectedTab = tab;
loadUrl(url, tab, callback);
}
function loadUrl(url, tab, callback) {
content.location.assign(url);
let browser = gBrowser.getBrowserForTab(tab);
if (browser.contentDocument.readyState === "complete") {
callback(tab, browser);
return;
}
let onLoad = function onLoad() {
browser.removeEventListener("load", onLoad, true);
callback(tab, browser);
};
browser.addEventListener("load", onLoad, true);
}
function openProfiler(tab, callback) {
let target = TargetFactory.forTab(tab);
gDevTools.showToolbox(target, "jsprofiler").then(callback);
}
function openConsole(tab, cb=function(){}) {
// This function was borrowed from webconsole/test/head.js
let target = TargetFactory.forTab(tab);
gDevTools.showToolbox(target, "webconsole").then(function (toolbox) {
let hud = toolbox.getCurrentPanel().hud;
hud.jsterm._lazyVariablesView = false;
cb(hud);
});
}
function closeProfiler(tab, callback) {
let target = TargetFactory.forTab(tab);
let toolbox = gDevTools.getToolbox(target);
toolbox.destroy().then(callback);
}
function setUp(url, callback=function(){}) {
Services.prefs.setBoolPref(PROFILER_ENABLED, true);
loadTab(url, function onTabLoad(tab, browser) {
openProfiler(tab, function onProfilerOpen() {
let target = TargetFactory.forTab(tab);
let panel = gDevTools.getToolbox(target).getPanel("jsprofiler");
callback(tab, browser, panel);
});
});
}
function tearDown(tab, callback=function(){}) {
closeProfiler(tab, function onProfilerClose() {
callback();
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
finish();
});
}
"use strict";

View File

@ -1,21 +0,0 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<meta charset='utf-8'/>
<title>console.profile from content</title>
</head>
<body>
<script>
console.profile();
var a = new Array(500);
while (a.length) {
a.shift();
}
console.profileEnd();
</script>
</body>
</html>

View File

@ -1,14 +0,0 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<meta charset='utf-8'/>
<title>Profiler Script Linking Test</title>
<script type="text/javascript" src="mock_profiler_bug_834878_script.js">
</script>
</head>
<body>
</body>
</html>

View File

@ -1,7 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function main() {
console.log("Hello, World!");
return 0;
}

View File

@ -9,11 +9,3 @@
- You want to make that choice consistent across the developer tools.
- A good criteria is the language in which you'd find the best
- documentation on web development on the web. -->
<!-- LOCALIZATION NOTE (profiler.importProfile): This string is displayed
- on a button that opens a dialog to import a saved profile data file. -->
<!ENTITY importProfile.label "Import…">
<!-- LOCALIZATION NOTE (profiler.startProfiler): This string is displayed
- on a button that starts a new profile -->
<!ENTITY startProfiler.tooltip "Start profiling">

View File

@ -2,8 +2,8 @@
# 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/.
# LOCALIZATION NOTE These strings are used inside the Debugger
# which is available from the Web Developer sub-menu -> 'Debugger'.
# LOCALIZATION NOTE These strings are used inside the Profiler
# which is available from the Web Developer sub-menu -> 'Profiler'.
# The correct localization of this file might be to keep it in
# English, or another language commonly spoken among web developers.
# You want to make that choice consistent across the developer tools.
@ -21,97 +21,10 @@ profiler.panelLabel=Profiler Panel
# LOCALIZATION NOTE (profiler2.commandkey, profiler.accesskey)
# Used for the menuitem in the tool menu
profiler2.commandkey=VK_F5
profiler.commandkey2=VK_F5
profiler.accesskey=P
# LOCALIZATION NOTE (profiler.tooltip2):
# This string is displayed in the tooltip of the tab when the profiler is
# displayed inside the developer tools window.
profiler.tooltip2=JavaScript Profiler
# LOCALIZATION NOTE (profiler.profileName):
# This string is the default name for new profiles. Its parameter is a number.
# For example: "Profile 1", "Profile 2", etc.
profiler.profileName=Profile %S
# LOCALIZATION NOTE (profiler.completeProfile):
# This string is displayed as a tab in the profiler UI. Clicking on it
# displays everything that the profiler has generated so far.
profiler.completeProfile=Complete Profile
# LOCALIZATION NOTE (profiler.sampleRange):
# This string is displayed as a tab in the profiler UI. Clicking on it
# displays a sample range of data selected by user themselves.
profiler.sampleRange=Sample Range
# LOCALIZATION NOTE (profiler.runningTime):
# This string is displayed as a table header in the profiler UI.
profiler.runningTime=Running Time
# LOCALIZATION NOTE (profiler.self):
# This string is displayed as a table header in the profiler UI.
# "Self" is how much time was spent doing work directly in that function.
# As opposed to the total time which is how much time was spent in that
# function and in functions it called.
profiler.self=Self
# LOCALIZATION NOTE (profiler.symbolName)
# This string is displayed as a table header in the profiler UI.
profiler.symbolName=Symbol Name
# LOCALIZATION NOTE (profiler.start)
# This string is displayed on the button that starts the profiler.
profiler.start=Start
# LOCALIZATION NOTE (profiler.stop)
# This string is displayed on the button that stops the profiler.
profiler.stop=Stop
# LOCALIZATION NOTE (profiler.loading)
# This string is displayed above the progress bar when the profiler
# is busy loading and parsing the report.
profiler.loading=Loading profile…
# LOCALIZATION NOTE (profiler.stateIdle)
# This string is used to show that the profile in question is in IDLE
# state meaning that it hasn't been started yet.
profiler.stateIdle=Idle
# LOCALIZATION NOTE (profiler.stateRunning)
# This string is used to show that the profile in question is in RUNNING
# state meaning that it has been started and currently gathering profile data.
profiler.stateRunning=Running
# LOCALIZATION NOTE (profiler.stateCompleted)
# This string is used to show that the profile in question is in COMPLETED
# state meaning that it has been started and stopped already.
profiler.stateCompleted=Completed
# LOCALIZATION NOTE (profiler.sidebarNotice)
# This string is displayed in the profiler sidebar when there are no
# existing profiles to show (usually happens when the user opens the
# profiler for the first time).
profiler.sidebarNotice=There are no profiles yet.
# LOCALIZATION NOTE (profiler.startProfilerString)
# This string is displayed on the profiler button when no active
# profiling sessions are running
profiler.startProfilerString=Start profiling
# LOCALIZATION NOTE (profiler.stopProfilerString)
# This string is displayed on the profiler button when an active
# profiling session is running
profiler.stopProfilerString=Stop profiling
# LOCALIZATION NOTE (profiler.save)
# This string is displayed as a label for a button that opens a Save File
# dialog where user can save generated profiler to a file.
profiler.save=Save
# LOCALIZATION NOTE (profiler.saveFileAs)
# This string as a title for a Save File dialog.
profiler.saveFileAs=Save Profile As
# LOCALIZATION NOTE (profiler.openFile)
# This string as a title for a Open File dialog.
profiler.openFile=Import Profile

View File

@ -2,4 +2,4 @@
* 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/. */
%include ../../shared/devtools/profiler.inc.css
%include ../../shared/devtools/profiler.inc.css

View File

@ -1,78 +1,4 @@
%if 0
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */
%endif
.profiler-sidebar-empty-notice {
max-width: 176px;
padding: 10px;
background-color: rgb(61, 69, 76);
color: white;
font-weight: bold;
}
.profiler-sidebar {
min-width: 196px;
}
.profiler-sidebar + .devtools-side-splitter {
border-color: transparent;
}
.profiler-sidebar .devtools-toolbar {
-moz-border-end: 1px solid;
}
.theme-dark .profiler-sidebar .devtools-toolbar {
-moz-border-end-color: black; /* Match the splitter. */
}
.theme-light .profiler-sidebar .devtools-toolbar {
-moz-border-end-color: #aaa; /* Match the splitter color. */
}
.profiler-sidebar-item {
padding: 3px 5px;
}
.profiler-sidebar-item, .side-menu-widget-item-contents {
cursor: default;
}
.profiler-sidebar-item > h3 {
font-size: 13px;
display: block;
cursor: pointer;
}
.profiler-sidebar-item > hbox {
margin-top: 2px;
}
.profiler-sidebar-item > hbox > a {
display: none;
text-decoration: underline;
cursor: pointer;
}
.selected [state=completed] .profiler-sidebar-item > hbox > a {
display: block;
}
.theme-dark .selected .profiler-sidebar-item > hbox {
color: #b6babf;
}
.theme-light .selected .profiler-sidebar-item > hbox {
color: #ebeced;
}
#profiler-start {
list-style-image: url("chrome://browser/skin/devtools/profiler-stopwatch.svg");
}
#profiler-start[checked] {
list-style-image: url("chrome://browser/skin/devtools/profiler-stopwatch-checked.svg");
}

View File

@ -1,245 +1,4 @@
/* 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 startedProfilers = 0;
var startTime = 0;
function getCurrentTime() {
return (new Date()).getTime() - startTime;
}
/**
* Creates a ProfilerActor. ProfilerActor provides remote access to the
* built-in profiler module.
*
* ProfilerActor.onGetProfile returns a JavaScript object with data
* generated by our built-in profiler moduele. It has the following
* format:
*
* {
* libs: string,
* meta: {
* interval: number,
* platform: string,
* (...)
* },
* threads: [
* {
* samples: [
* {
* frames: [
* {
* line: number,
* location: string
* }
* ],
* name: string
* responsiveness: number (in ms)
* time: number (nspr time)
* }
* ]
* }
* ]
* }
*
*/
function ProfilerActor(aConnection)
{
this._profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
this._started = false;
this._observedEvents = [];
}
ProfilerActor.prototype = {
actorPrefix: "profiler",
disconnect: function() {
for (var event of this._observedEvents) {
Services.obs.removeObserver(this, event);
}
this.stopProfiler();
this._profiler = null;
},
stopProfiler: function() {
// We stop the profiler only after the last client has
// stopped profiling. Otherwise there's a problem where
// we stop the profiler as soon as you close the devtools
// panel in one tab even though there might be other
// profiler instances running in other tabs.
if (!this._started) {
return;
}
this._started = false;
startedProfilers -= 1;
if (startedProfilers <= 0) {
this._profiler.StopProfiler();
}
},
onStartProfiler: function(aRequest) {
this._profiler.StartProfiler(aRequest.entries, aRequest.interval,
aRequest.features, aRequest.features.length);
this._started = true;
startedProfilers += 1;
startTime = (new Date()).getTime();
return { "msg": "profiler started" }
},
onStopProfiler: function(aRequest) {
this.stopProfiler();
return { "msg": "profiler stopped" }
},
onGetProfileStr: function(aRequest) {
var profileStr = this._profiler.GetProfile();
return { "profileStr": profileStr }
},
onGetProfile: function(aRequest) {
var profile = this._profiler.getProfileData();
return { "profile": profile, "currentTime": getCurrentTime() }
},
onIsActive: function(aRequest) {
var isActive = this._profiler.IsActive();
var currentTime = isActive ? getCurrentTime() : null;
return { "isActive": isActive, "currentTime": currentTime }
},
onGetFeatures: function(aRequest) {
var features = this._profiler.GetFeatures([]);
return { "features": features }
},
onGetSharedLibraryInformation: function(aRequest) {
var sharedLibraries = this._profiler.getSharedLibraryInformation();
return { "sharedLibraryInformation": sharedLibraries }
},
onRegisterEventNotifications: function(aRequest) {
let registered = [];
for (var event of aRequest.events) {
if (this._observedEvents.indexOf(event) != -1)
continue;
Services.obs.addObserver(this, event, false);
this._observedEvents.push(event);
registered.push(event);
}
return { registered: registered }
},
onUnregisterEventNotifications: function(aRequest) {
let unregistered = [];
for (var event of aRequest.events) {
let idx = this._observedEvents.indexOf(event);
if (idx == -1)
continue;
Services.obs.removeObserver(this, event);
this._observedEvents.splice(idx, 1);
unregistered.push(event);
}
return { unregistered: unregistered }
},
observe: DevToolsUtils.makeInfallible(function(aSubject, aTopic, aData) {
/*
* this.conn.send can only transmit acyclic values. However, it is
* idiomatic for wrapped JS objects like aSubject (and possibly aData?)
* to have a 'wrappedJSObject' property pointing to themselves.
*
* this.conn.send also assumes that it can retain the object it is
* passed to be handled on later event ticks; and that it's okay to
* freeze it. Since we don't really know what aSubject and aData are,
* we need to pass this.conn.send a copy of them, not the originals.
*
* We break the cycle and make the copy by JSON.stringifying those
* values with a replacer that omits properties known to introduce
* cycles, and then JSON.parsing the result. This spends processor
* time, but it's simple.
*/
function cycleBreaker(key, value) {
if (key === 'wrappedJSObject') {
return undefined;
}
return value;
}
/*
* If these values are objects with a non-null 'wrappedJSObject' property
* and aren't Xrays, use their .wrappedJSObject. Otherwise, use the value
* unchanged.
*/
aSubject = (aSubject && !Cu.isXrayWrapper(aSubject) && aSubject.wrappedJSObject) || aSubject;
aData = (aData && !Cu.isXrayWrapper(aData) && aData.wrappedJSObject) || aData;
let subj = JSON.parse(JSON.stringify(aSubject, cycleBreaker));
let data = JSON.parse(JSON.stringify(aData, cycleBreaker));
let send = (extra) => {
data = data || {};
if (extra)
data.extra = extra;
this.conn.send({
from: this.actorID,
type: "eventNotification",
event: aTopic,
subject: subj,
data: data
});
}
if (aTopic !== "console-api-profiler")
return void send();
// If the event was generated from console.profile or
// console.profileEnd we need to start the profiler
// right away and only then notify our client. Otherwise,
// we'll lose precious samples.
let name = subj.arguments[0];
if (subj.action === "profile") {
let resp = this.onIsActive();
if (resp.isActive) {
return void send({
name: name,
currentTime: resp.currentTime,
action: "profile"
});
}
this.onStartProfiler({
entries: 1000000,
interval: 1,
features: ["js"]
});
return void send({ currentTime: 0, action: "profile", name: name });
}
if (subj.action === "profileEnd") {
let resp = this.onGetProfile();
resp.action = "profileEnd";
resp.name = name;
send(resp);
}
return undefined; // Otherwise xpcshell tests fail.
}, "ProfilerActor.prototype.observe"),
};
/**
* The request types this actor can handle.
*/
ProfilerActor.prototype.requestTypes = {
"startProfiler": ProfilerActor.prototype.onStartProfiler,
"stopProfiler": ProfilerActor.prototype.onStopProfiler,
"getProfileStr": ProfilerActor.prototype.onGetProfileStr,
"getProfile": ProfilerActor.prototype.onGetProfile,
"isActive": ProfilerActor.prototype.onIsActive,
"getFeatures": ProfilerActor.prototype.onGetFeatures,
"getSharedLibraryInformation": ProfilerActor.prototype.onGetSharedLibraryInformation,
"registerEventNotifications": ProfilerActor.prototype.onRegisterEventNotifications,
"unregisterEventNotifications": ProfilerActor.prototype.onUnregisterEventNotifications
};
DebuggerServer.addGlobalActor(ProfilerActor, "profilerActor");

View File

@ -1,66 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
function connectClient(callback) {
let client = new DebuggerClient(DebuggerServer.connectPipe());
client.connect(function () {
client.listTabs(function(response) {
callback(client, response.profilerActor);
});
});
}
function run_test()
{
// Ensure the profiler is not running when the test starts (it could
// happen if the MOZ_PROFILER_STARTUP environment variable is set)
Profiler.StopProfiler();
DebuggerServer.init(function () { return true; });
DebuggerServer.addBrowserActors();
connectClient((client1, actor1) => {
connectClient((client2, actor2) => {
activate_first(client1, actor1, client2, actor2);
});
})
do_test_pending();
}
function activate_first(client1, actor1, client2, actor2) {
// Start the profiler on the first connection....
client1.request({ to: actor1, type: "startProfiler", features: ['js']}, startResponse => {
// Profiler should be active now.
do_check_true(Profiler.IsActive());
// But on the next connection just make sure the actor has been
// instantiated.
client2.request({ to: actor2, type: "getFeatures" }, featureResponse => {
let connectionClosed = DebuggerServer._connectionClosed;
DebuggerServer._connectionClosed = function(conn) {
connectionClosed.call(this, conn);
// Client1 is the only actor that started the profiler,
// it shouldn't be active anymore.
do_check_false(Profiler.IsActive());
DebuggerServer._connectionClosed = function(conn) {
connectionClosed.call(this, conn);
// Now there are no open clients at all, it should *definitely*
// be deactivated by now.
do_check_false(Profiler.IsActive());
do_test_finished();
}
client2.close();
};
client1.close();
});
});
}

View File

@ -1,191 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
function run_test()
{
// Ensure the profiler is not running when the test starts (it could
// happen if the MOZ_PROFILER_STARTUP environment variable is set)
Profiler.StopProfiler();
DebuggerServer.init(function () { return true; });
DebuggerServer.addBrowserActors();
var client = new DebuggerClient(DebuggerServer.connectPipe());
client.connect(function () {
client.listTabs(function(aResponse) {
test_profiler_actor(client, aResponse.profilerActor);
});
});
do_test_pending();
}
function test_profiler_actor(aClient, aProfiler)
{
aClient.request({ to: aProfiler, type: "isActive" }, function (aResponse) {
do_check_false(aResponse.isActive);
aClient.request({ to: aProfiler, type: "getFeatures" }, function (aResponse) {
var features = Profiler.GetFeatures([]);
do_check_eq(aResponse.features.length, features.length);
for (var i = 0; i < features.length; i++)
do_check_eq(aResponse.features[i], features[i]);
aClient.request({ to: aProfiler, type: "startProfiler", features: ['js'] }, function (aResponse) {
do_check_eq(typeof aResponse.msg, "string");
aClient.request({ to: aProfiler, type: "isActive" }, function (aResponse) {
do_check_true(aResponse.isActive);
aClient.request({ to: aProfiler, type: "getSharedLibraryInformation" }, function (aResponse) {
do_check_eq(typeof aResponse.sharedLibraryInformation, "string");
try {
JSON.parse(aResponse.sharedLibraryInformation);
} catch(e) {
do_throw(e.toString(), Components.stack.caller);
}
test_event_notifications(aClient, aProfiler);
});
});
});
});
});
}
function test_event_notifications(aClient, aProfiler)
{
aClient.request({ to: aProfiler, type: "registerEventNotifications", events: ["foo", "bar"] }, function (aResponse) {
do_check_eq(typeof aResponse.registered, "object");
do_check_eq(aResponse.registered.length, 2);
do_check_eq(aResponse.registered[0], "foo");
do_check_eq(aResponse.registered[1], "bar");
aClient.request({ to: aProfiler, type: "registerEventNotifications", events: ["foo"] }, function (aResponse) {
do_check_eq(typeof aResponse.registered, "object");
do_check_eq(aResponse.registered.length, 0);
aClient.addListener("eventNotification", function (aType, aData) {
do_check_eq(aType, "eventNotification");
do_check_eq(aData.event, "foo");
do_check_eq(typeof aData.subject, "object");
do_check_eq(aData.subject.foo, "foo");
do_check_eq(aData.data, "foo");
});
var subject = { foo: "foo" };
subject.wrappedJSObject = subject;
Services.obs.notifyObservers(subject, "foo", "foo");
aClient.request({ to: aProfiler, type: "unregisterEventNotifications", events: ["foo", "bar", "qux"] }, function (aResponse) {
do_check_eq(typeof aResponse.unregistered, "object");
do_check_eq(aResponse.unregistered.length, 2);
do_check_eq(aResponse.unregistered[0], "foo");
do_check_eq(aResponse.unregistered[1], "bar");
// All events being now unregistered, sending an event shouldn't
// do anything. If it does, the eventNotification listener above
// will catch the event and fail on the aData.event test.
Services.obs.notifyObservers(null, "bar", null);
test_profile(aClient, aProfiler);
});
});
});
}
function test_profile(aClient, aProfiler)
{
function attempt(aDelayMS)
{
// No idea why, but Components.stack.sourceLine returns null.
var funcLine = Components.stack.lineNumber - 3;
// Spin for the requested time, then take a sample.
var start = Date.now();
var stack;
do_print("attempt: delay = " + aDelayMS);
while (Date.now() - start < aDelayMS) { stack = Components.stack; }
do_print("attempt: before getProfile");
aClient.request({ to: aProfiler, type: "getProfile" }, function (aResponse) {
// Any valid getProfile response should have the following top
// level structure.
do_check_eq(typeof aResponse.profile, "object");
do_check_eq(typeof aResponse.profile.meta, "object");
do_check_eq(typeof aResponse.profile.meta.platform, "string");
do_check_eq(typeof aResponse.profile.threads, "object");
do_check_eq(typeof aResponse.profile.threads[0], "object");
do_check_eq(typeof aResponse.profile.threads[0].samples, "object");
// At this point, we may or may not have samples, depending on
// whether the spin loop above has given the profiler enough time
// to get started.
if (aResponse.profile.threads[0].samples.length == 0) {
if (aDelayMS < 20000) {
// Double the spin-wait time and try again.
do_print("attempt: no samples, going around again");
return attempt(aDelayMS * 2);
} else {
// We've waited long enough, so just fail.
do_print("attempt: waited 20000ms, but no samples were collected. Giving up.");
do_check_true(false);
return;
}
}
// Now check the samples. At least one sample is expected to
// have been in the busy wait above.
do_print("attempt: got a profile that contains samples");
let location = stack.name + " (" + stack.filename + ":" + funcLine + ")";
do_check_true(aResponse.profile.threads[0].samples.some(function(sample) {
return typeof sample.frames == "object" &&
sample.frames.length != 0 &&
sample.frames.some(function(f) {
return (f.line == stack.lineNumber) &&
(f.location == location);
});
}));
aClient.request({ to: aProfiler, type: "stopProfiler" }, function (aResponse) {
do_check_eq(typeof aResponse.msg, "string");
aClient.request({ to: aProfiler, type: "isActive" }, function (aResponse) {
do_check_false(aResponse.isActive);
aClient.close(function() {
test_profiler_status();
});
});
});
});
}
// Start off with a 100 millisecond delay.
attempt(100);
}
function test_profiler_status()
{
var connectionClosed = DebuggerServer._connectionClosed;
var client = new DebuggerClient(DebuggerServer.connectPipe());
client.connect(() => {
client.listTabs((aResponse) => {
DebuggerServer._connectionClosed = function (conn) {
connectionClosed.call(this, conn);
// Check that closing the last (only?) connection stops the profiler.
do_check_false(Profiler.IsActive());
do_test_finished();
}
var profiler = aResponse.profilerActor;
do_check_false(Profiler.IsActive());
client.request({
to: profiler,
type: "startProfiler",
features: []
}, function (aResponse) {
do_check_true(Profiler.IsActive());
client.close();
});
});
});
}

View File

@ -179,10 +179,6 @@ reason = bug 820380
skip-if = toolkit == "gonk"
reason = bug 820380
[test_breakpointstore.js]
[test_profiler_actor.js]
[test_profiler_activation.js]
skip-if = toolkit == "gonk"
reason = bug 820380
[test_unsafeDereference.js]
[test_add_actors.js]
[test_trace_actor-01.js]