mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 17:23:59 +00:00
Bug 879008 - Remove the old Profiler frontend, r=rcampbell
This commit is contained in:
parent
75c2d1d5a9
commit
b5f375d725
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -1,167 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
let { Cu } = require("chrome");
|
||||
let { defer } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||
let EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
const { PROFILE_IDLE, PROFILE_COMPLETED, PROFILE_RUNNING } = require("devtools/profiler/consts");
|
||||
|
||||
/**
|
||||
* An implementation of a profile visualization that uses Cleopatra.
|
||||
* It consists of an iframe with Cleopatra loaded in it and some
|
||||
* surrounding meta-data (such as UIDs).
|
||||
*
|
||||
* Cleopatra is also an event emitter. It emits the following events:
|
||||
* - ready, when Cleopatra is done loading (you can also check the isReady
|
||||
* property to see if a particular instance has been loaded yet.
|
||||
*
|
||||
* @param number uid
|
||||
* Unique ID for this profile.
|
||||
* @param ProfilerPanel panel
|
||||
* A reference to the container panel.
|
||||
*/
|
||||
function Cleopatra(panel, opts) {
|
||||
let doc = panel.document;
|
||||
let win = panel.window;
|
||||
let { uid, name } = opts;
|
||||
let spd = opts.showPlatformData;
|
||||
let ext = opts.external;
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.isReady = false;
|
||||
this.isStarted = false;
|
||||
this.isFinished = false;
|
||||
|
||||
this.panel = panel;
|
||||
this.uid = uid;
|
||||
this.name = name;
|
||||
|
||||
this.iframe = doc.createElement("iframe");
|
||||
this.iframe.setAttribute("flex", "1");
|
||||
this.iframe.setAttribute("id", "profiler-cleo-" + uid);
|
||||
this.iframe.setAttribute("src", "cleopatra.html?uid=" + uid + "&spd=" + spd + "&ext=" + ext);
|
||||
this.iframe.setAttribute("hidden", "true");
|
||||
|
||||
// Append our iframe and subscribe to postMessage events.
|
||||
// They'll tell us when the underlying page is done loading
|
||||
// or when user clicks on start/stop buttons.
|
||||
|
||||
doc.getElementById("profiler-report").appendChild(this.iframe);
|
||||
win.addEventListener("message", (event) => {
|
||||
if (parseInt(event.data.uid, 10) !== parseInt(this.uid, 10)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.data.status) {
|
||||
case "loaded":
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
break;
|
||||
case "displaysource":
|
||||
this.panel.displaySource(event.data.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Cleopatra.prototype = {
|
||||
/**
|
||||
* Returns a contentWindow of the iframe pointing to Cleopatra
|
||||
* if it exists and can be accessed. Otherwise returns null.
|
||||
*/
|
||||
get contentWindow() {
|
||||
if (!this.iframe) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return this.iframe.contentWindow;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
show: function () {
|
||||
this.iframe.removeAttribute("hidden");
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
this.iframe.setAttribute("hidden", true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send raw profiling data to Cleopatra for parsing.
|
||||
*
|
||||
* @param object data
|
||||
* Raw profiling data from the SPS Profiler.
|
||||
* @param function onParsed
|
||||
* A callback to be called when Cleopatra finishes
|
||||
* parsing and displaying results.
|
||||
*
|
||||
*/
|
||||
parse: function (data, onParsed) {
|
||||
if (!this.isReady) {
|
||||
return void this.on("ready", this.parse.bind(this, data, onParsed));
|
||||
}
|
||||
|
||||
this.message({ task: "receiveProfileData", rawProfile: data }).then(() => {
|
||||
let poll = () => {
|
||||
let wait = this.panel.window.setTimeout.bind(null, poll, 100);
|
||||
let trail = this.contentWindow.gBreadcrumbTrail;
|
||||
|
||||
if (!trail) {
|
||||
return wait();
|
||||
}
|
||||
|
||||
if (!trail._breadcrumbs || !trail._breadcrumbs.length) {
|
||||
return wait();
|
||||
}
|
||||
|
||||
onParsed();
|
||||
};
|
||||
|
||||
poll();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a message to Cleopatra instance. If a message cannot be
|
||||
* sent, this method queues it for later.
|
||||
*
|
||||
* @param object data JSON data to send (must be serializable)
|
||||
* @return promise
|
||||
*/
|
||||
message: function (data) {
|
||||
let deferred = defer();
|
||||
data = JSON.stringify(data);
|
||||
|
||||
let send = () => {
|
||||
if (!this.contentWindow)
|
||||
setTimeout(send, 50);
|
||||
|
||||
this.contentWindow.postMessage(data, "*");
|
||||
deferred.resolve();
|
||||
};
|
||||
|
||||
send();
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys the ProfileUI instance.
|
||||
*/
|
||||
destroy: function () {
|
||||
this.isReady = null;
|
||||
this.panel = null;
|
||||
this.uid = null;
|
||||
this.iframe = null;
|
||||
this.messages = null;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Cleopatra;
|
||||
|
@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Firefox Profiler (SPS)</title>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="profiler/cleopatra/css/ui.css">
|
||||
<link rel="stylesheet" type="text/css" href="profiler/cleopatra/css/tree.css">
|
||||
<link rel="stylesheet" type="text/css" href="profiler/cleopatra/css/devtools.css">
|
||||
|
||||
<script src="profiler/cleopatra/js/strings.js"></script>
|
||||
<script src="profiler/cleopatra/js/parser.js"></script>
|
||||
<script src="profiler/cleopatra/js/tree.js"></script>
|
||||
<script src="profiler/cleopatra/js/ui.js"></script>
|
||||
<script src="profiler/cleopatra/js/ProgressReporter.js"></script>
|
||||
<script src="profiler/cleopatra/js/devtools.js"></script>
|
||||
</head>
|
||||
|
||||
<body onload="notifyParent('loaded');">
|
||||
<script>
|
||||
initUI();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,11 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#mainarea {
|
||||
}
|
||||
|
||||
/* De-emphasize chrome functions */
|
||||
.resourceIcon[data-resource^=otherhost_] + .functionName {
|
||||
color: #999;
|
||||
}
|
@ -1,236 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
.treeViewContainer {
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
line-height: 16px;
|
||||
height: 100%;
|
||||
outline: none; /* override the browser's focus styling */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.treeHeader {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.treeColumnHeader {
|
||||
position: absolute;
|
||||
display: block;
|
||||
background: linear-gradient(#FFF 45%, #EEE 60%);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
top: 0;
|
||||
height: 15px;
|
||||
line-height: 15px;
|
||||
border: 0 solid #CCC;
|
||||
border-bottom-width: 1px;
|
||||
text-indent: 5px;
|
||||
}
|
||||
|
||||
.treeColumnHeader:not(:last-child) {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
.treeColumnHeader0 {
|
||||
left: 0;
|
||||
width: 86px;
|
||||
}
|
||||
|
||||
.treeColumnHeader1 {
|
||||
left: 99px;
|
||||
width: 35px;
|
||||
}
|
||||
|
||||
.treeColumnHeader0,
|
||||
.treeColumnHeader1 {
|
||||
text-align: right;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.treeColumnHeader2 {
|
||||
left: 147px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.treeViewVerticalScrollbox {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.treeViewNode,
|
||||
.treeViewHorizontalScrollbox {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.treeViewNode {
|
||||
min-width: -moz-min-content;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.treeViewHorizontalScrollbox {
|
||||
padding-left: 150px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.treeViewVerticalScrollbox,
|
||||
.treeViewHorizontalScrollbox {
|
||||
background: linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF);
|
||||
background-size: 100px 32px;
|
||||
}
|
||||
|
||||
.leftColumnBackground {
|
||||
background: linear-gradient(left, transparent, transparent 98px, #CCC 98px, #CCC 99px, transparent 99px),
|
||||
linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF);
|
||||
background-size: auto, 100px 32px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 146px;
|
||||
min-height: 100%;
|
||||
border-right: 1px solid #CCC;
|
||||
}
|
||||
|
||||
.sampleCount,
|
||||
.samplePercentage,
|
||||
.selfSampleCount {
|
||||
position: absolute;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.sampleCount {
|
||||
left: 2px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.samplePercentage {
|
||||
left: 55px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.selfSampleCount {
|
||||
left: 98px;
|
||||
width: 45px;
|
||||
padding-right: 2px;
|
||||
border: solid #CCC;
|
||||
border-width: 0 1px;
|
||||
}
|
||||
|
||||
.libraryName {
|
||||
margin-left: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.treeViewNode > .treeViewNodeList {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.treeViewNode.collapsed > .treeViewNodeList {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.treeLine {
|
||||
/* extend the selection background almost infinitely to the left */
|
||||
margin-left: -10000px;
|
||||
padding-left: 10000px;
|
||||
}
|
||||
|
||||
.treeLine.selected {
|
||||
color: black;
|
||||
background-color: -moz-dialog;
|
||||
}
|
||||
|
||||
.treeLine.selected > .sampleCount {
|
||||
background-color: inherit;
|
||||
margin-left: -2px;
|
||||
padding-left: 2px;
|
||||
padding-right: 95px;
|
||||
margin-right: -95px;
|
||||
}
|
||||
|
||||
.treeViewContainer:focus .treeLine.selected {
|
||||
color: highlighttext;
|
||||
background-color: highlight;
|
||||
}
|
||||
|
||||
.treeViewContainer:focus .treeLine.selected > .libraryName {
|
||||
color: #CCC;
|
||||
}
|
||||
|
||||
.expandCollapseButton,
|
||||
.focusCallstackButton {
|
||||
background: none 0 0 no-repeat transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
overflow: hidden;
|
||||
vertical-align: top;
|
||||
color: transparent;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.expandCollapseButton {
|
||||
background-image: url(../images/treetwisty.svg);
|
||||
}
|
||||
|
||||
.focusCallstackButton {
|
||||
background-image: url(../images/circlearrow.svg);
|
||||
margin-left: 5px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.expandCollapseButton:active:hover,
|
||||
.focusCallstackButton:active:hover {
|
||||
background-position: -16px 0;
|
||||
}
|
||||
|
||||
.treeViewNode.collapsed > .treeLine > .expandCollapseButton {
|
||||
background-position: 0 -16px;
|
||||
}
|
||||
|
||||
.treeViewNode.collapsed > .treeLine > .expandCollapseButton:active:hover {
|
||||
background-position: -16px -16px;
|
||||
}
|
||||
|
||||
.treeViewContainer:focus .treeLine.selected > .expandCollapseButton,
|
||||
.treeViewContainer:focus .treeLine.selected > .focusCallstackButton {
|
||||
background-position: -32px 0;
|
||||
}
|
||||
|
||||
.treeViewContainer:focus .treeViewNode.collapsed > .treeLine.selected > .expandCollapseButton {
|
||||
background-position: -32px -16px;
|
||||
}
|
||||
|
||||
.treeViewContainer:focus .treeLine.selected > .expandCollapseButton:active:hover,
|
||||
.treeViewContainer:focus .treeLine.selected > .focusCallstackButton:active:hover {
|
||||
background-position: -48px 0;
|
||||
}
|
||||
|
||||
.treeViewContainer:focus .treeViewNode.collapsed > .treeLine.selected > .expandCollapseButton:active:hover {
|
||||
background-position: -48px -16px;
|
||||
}
|
||||
|
||||
.treeViewNode.leaf > * > .expandCollapseButton {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.treeLine:hover > .focusCallstackButton {
|
||||
visibility: visible;
|
||||
}
|
@ -1,340 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Lucida Grande", sans-serif;
|
||||
font-size: 11px;
|
||||
}
|
||||
#mainarea {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.finishedProfilePane,
|
||||
.finishedProfilePaneBackgroundCover,
|
||||
.profileEntryPane,
|
||||
.profileProgressPane {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.profileEntryPane {
|
||||
overflow: auto;
|
||||
}
|
||||
.profileEntryPane,
|
||||
.profileProgressPane {
|
||||
padding: 20px;
|
||||
background-color: rgb(229,229,229);
|
||||
background-image: url(../images/noise.png),
|
||||
linear-gradient(rgba(255,255,255,.5),rgba(255,255,255,.2));
|
||||
text-shadow: rgba(255, 255, 255, 0.4) 0 1px;
|
||||
}
|
||||
.profileEntryPane h1 {
|
||||
margin-top: 0;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.profileEntryPane input[type="file"] {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.profileProgressPane a {
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
left: 30%;
|
||||
width: 40%;
|
||||
height: 16px;
|
||||
}
|
||||
.profileProgressPane progress {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 30%;
|
||||
width: 40%;
|
||||
height: 16px;
|
||||
}
|
||||
.finishedProfilePaneBackgroundCover {
|
||||
animation: darken 300ms cubic-bezier(0, 0, 1, 0);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.finishedProfilePane {
|
||||
animation: appear 300ms ease-out;
|
||||
}
|
||||
|
||||
@keyframes darken {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes appear {
|
||||
from {
|
||||
transform: scale(0.3);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
.breadcrumbTrail {
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 29px;
|
||||
left: 0;
|
||||
background: linear-gradient(#FFF 50%, #F3F3F3 55%);
|
||||
border-bottom: 1px solid #CCC;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.breadcrumbTrailItem {
|
||||
background: linear-gradient(#FFF 50%, #F3F3F3 55%);
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
float: left;
|
||||
line-height: 29px;
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
border-right: 1px solid #CCC;
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
@keyframes slide-out {
|
||||
from {
|
||||
margin-left: -270px;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
margin-left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes slide-out {
|
||||
from {
|
||||
margin-left: -270px;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
margin-left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.breadcrumbTrailItem:not(:first-child) {
|
||||
animation: slide-out;
|
||||
animation-duration: 400ms;
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
.breadcrumbTrailItem.selected {
|
||||
background: linear-gradient(#E5E5E5 50%, #DADADA 55%);
|
||||
}
|
||||
.breadcrumbTrailItem:not(.selected):active:hover {
|
||||
background: linear-gradient(#F2F2F2 50%, #E6E6E6 55%);
|
||||
}
|
||||
.breadcrumbTrailItem.deleted {
|
||||
transition: 400ms ease-out;
|
||||
transition-property: opacity, margin-left;
|
||||
opacity: 0;
|
||||
margin-left: -270px;
|
||||
}
|
||||
.treeContainer {
|
||||
/*For asbolute position child*/
|
||||
position: relative;
|
||||
}
|
||||
.tree {
|
||||
height: 100%;
|
||||
}
|
||||
#sampleBar {
|
||||
position: absolute;
|
||||
float: right;
|
||||
left: auto;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
}
|
||||
#fileList {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 360px;
|
||||
width: 199px;
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: #DBDFE7;
|
||||
border-right: 1px solid #BBB;
|
||||
cursor: pointer;
|
||||
}
|
||||
#infoBar dl {
|
||||
margin: 0;
|
||||
}
|
||||
#infoBar dt,
|
||||
#infoBar dd {
|
||||
display: inline;
|
||||
}
|
||||
#infoBar dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
#infoBar dt::after {
|
||||
content: " ";
|
||||
white-space: pre;
|
||||
}
|
||||
#infoBar dd {
|
||||
margin-left: 0;
|
||||
}
|
||||
#infoBar dd::after {
|
||||
content: "\a";
|
||||
white-space:pre;
|
||||
}
|
||||
.sideBar {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 200px;
|
||||
height: 480px;
|
||||
overflow: auto;
|
||||
padding: 3px;
|
||||
background: #EEE;
|
||||
border-top: 1px solid #BBB;
|
||||
border-right: 1px solid #BBB;
|
||||
}
|
||||
.sideBar h2 {
|
||||
font-size: 1em;
|
||||
padding: 1px 3px;
|
||||
margin: 3px -3px;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border: solid #CCC;
|
||||
border-width: 1px 0;
|
||||
}
|
||||
.sideBar h2:first-child {
|
||||
margin-top: -4px;
|
||||
}
|
||||
.sideBar ul {
|
||||
margin: 2px 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
.pluginview {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background-color: white;
|
||||
}
|
||||
.pluginviewIFrame {
|
||||
border-style: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.histogram {
|
||||
position: relative;
|
||||
height: 60px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
border-bottom: 1px solid #CCC;
|
||||
background: linear-gradient(#EEE, #CCC);
|
||||
}
|
||||
.histogramHilite {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.histogramHilite:not(.collapsed) {
|
||||
background: rgba(150, 150, 150, 0.5);
|
||||
}
|
||||
.histogramMouseMarker {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
.histogramMouseMarker:not(.collapsed) {
|
||||
background: rgba(0, 0, 150, 0.7);
|
||||
}
|
||||
#iconbox {
|
||||
display: none;
|
||||
}
|
||||
#filter, #showall {
|
||||
cursor: pointer;
|
||||
}
|
||||
.markers {
|
||||
display: none;
|
||||
}
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
.fileListItem {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 40px;
|
||||
text-indent: 8px;
|
||||
}
|
||||
.fileListItem.selected {
|
||||
background: linear-gradient(#4B91D7 1px, #5FA9E4 1px, #5FA9E4 2px, #58A0DE 3px, #2B70C7 39px, #2763B4 39px);
|
||||
color: #FFF;
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.fileListItemTitle {
|
||||
display: block;
|
||||
padding-top: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.fileListItemDescription {
|
||||
display: block;
|
||||
line-height: 15px;
|
||||
font-size: 9px;
|
||||
}
|
||||
.busyCover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
background: rgba(120, 120, 120, 0.2);
|
||||
transition: 200ms ease-in-out;
|
||||
transition-property: visibility, opacity;
|
||||
}
|
||||
.busyCover.busy {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
.busyCover::before {
|
||||
content: url(../images/throbber.svg);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -12px;
|
||||
}
|
||||
label {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
.videoPane {
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
}
|
||||
.video {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="64" height="16" viewBox="0 0 64 16">
|
||||
<defs>
|
||||
<mask id="arrowInCircle" maskContentUnits="userSpaceOnUse">
|
||||
<circle cx="8" cy="8" r="6" fill="white"/>
|
||||
<rect x="4.5" y="7" width="3.5" height="2" fill="black"/>
|
||||
<polyline points="8 4 12 8 8 12" fill="black"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<g fill="#888">
|
||||
<rect x="0" y="0" width="16" height="16" mask="url(#arrowInCircle)"/>
|
||||
</g>
|
||||
<g fill="#444" transform="translate(16,0)">
|
||||
<rect x="0" y="0" width="16" height="16" mask="url(#arrowInCircle)"/>
|
||||
</g>
|
||||
<g fill="#FFF" transform="translate(32,0)">
|
||||
<rect x="0" y="0" width="16" height="16" mask="url(#arrowInCircle)"/>
|
||||
</g>
|
||||
<g fill="rgba(255, 255, 255, 0.7)" transform="translate(48,0)">
|
||||
<rect x="0" y="0" width="16" height="16" mask="url(#arrowInCircle)"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,23 +0,0 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="24" height="24" viewBox="0 0 64 64">
|
||||
<g>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(0, 32, 32)" fill="#BBB"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(30, 32, 32)" fill="#AAA"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(60, 32, 32)" fill="#999"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(90, 32, 32)" fill="#888"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(120, 32, 32)" fill="#777"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(150, 32, 32)" fill="#666"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(180, 32, 32)" fill="#555"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(210, 32, 32)" fill="#444"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(240, 32, 32)" fill="#333"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(270, 32, 32)" fill="#222"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(300, 32, 32)" fill="#111"/>
|
||||
<rect x="30" y="4" width="4" height="15" transform="rotate(330, 32, 32)" fill="#000"/>
|
||||
<animateTransform attributeName="transform" type="rotate" calcMode="discrete" values="0 32 32;30 32 32;60 32 32;90 32 32;120 32 32;150 32 32;180 32 32;210 32 32;240 32 32;270 32 32;300 32 32;330 32 32" dur="0.8s" repeatCount="indefinite"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.7 KiB |
@ -1,32 +0,0 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="64" height="32" viewBox="0 0 64 32">
|
||||
<g fill="#888">
|
||||
<polyline points="3 4 12 4 7.5 12"/>
|
||||
<g transform="translate(0,16)">
|
||||
<polyline points="3 4 12 4 7.5 12" transform="rotate(-90, 7.5, 7.5)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#444" transform="translate(16,0)">
|
||||
<polyline points="3 4 12 4 7.5 12"/>
|
||||
<g transform="translate(0,16)">
|
||||
<polyline points="3 4 12 4 7.5 12" transform="rotate(-90, 7.5, 7.5)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="#FFF" transform="translate(32,0)">
|
||||
<polyline points="3 4 12 4 7.5 12"/>
|
||||
<g transform="translate(0,16)">
|
||||
<polyline points="3 4 12 4 7.5 12" transform="rotate(-90, 7.5, 7.5)"/>
|
||||
</g>
|
||||
</g>
|
||||
<g fill="rgba(255, 255, 255, 0.7)" transform="translate(48,0)">
|
||||
<polyline points="3 4 12 4 7.5 12"/>
|
||||
<g transform="translate(0,16)">
|
||||
<polyline points="3 4 12 4 7.5 12" transform="rotate(-90, 7.5, 7.5)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,187 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* ProgressReporter
|
||||
*
|
||||
* This class is used by long-winded tasks to report progress to observers.
|
||||
* If a task has subtasks that want to report their own progress, these
|
||||
* subtasks can have their own progress reporters which are hooked up to the
|
||||
* parent progress reporter, resulting in a tree structure. A parent progress
|
||||
* reporter will calculate its progress value as a weighted sum of its
|
||||
* subreporters' progress values.
|
||||
*
|
||||
* A progress reporter has a state, an action, and a progress value.
|
||||
*
|
||||
* - state is one of STATE_WAITING, STATE_DOING and STATE_FINISHED.
|
||||
* - action is a string that describes the current task.
|
||||
* - progress is the progress value as a number between 0 and 1, or NaN if
|
||||
* indeterminate.
|
||||
*
|
||||
* A progress reporter starts out in the WAITING state. The DOING state is
|
||||
* entered with the begin method which also sets the action. While the task is
|
||||
* executing, the progress value can be updated with the setProgress method.
|
||||
* When a task has finished, it can call the finish method which is just a
|
||||
* shorthand for setProgress(1); this will set the state to FINISHED.
|
||||
*
|
||||
* Progress observers can be added with the addListener method which takes a
|
||||
* function callback. Whenever the progress value or state change, all
|
||||
* listener callbacks will be called with the progress reporter object. The
|
||||
* observer can get state, progress value and action by calling the getter
|
||||
* methods getState(), getProgress() and getAction().
|
||||
*
|
||||
* Creating child progress reporters for subtasks can be done with the
|
||||
* addSubreporter(s) methods. If a progress reporter has subreporters, normal
|
||||
* progress report functions (setProgress and finish) can no longer be called.
|
||||
* Instead, the parent reporter will listen to progress changes on its
|
||||
* subreporters and update its state automatically, and then notify its own
|
||||
* listeners.
|
||||
* When adding a subreporter, you are expected to provide an estimated
|
||||
* duration for the subtask. This value will be used as a weight when
|
||||
* calculating the progress of the parent reporter.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const gDebugExpectedDurations = false;
|
||||
|
||||
function ProgressReporter() {
|
||||
this._observers = [];
|
||||
this._subreporters = [];
|
||||
this._subreporterExpectedDurationsSum = 0;
|
||||
this._progress = 0;
|
||||
this._state = ProgressReporter.STATE_WAITING;
|
||||
this._action = "";
|
||||
}
|
||||
|
||||
ProgressReporter.STATE_WAITING = 0;
|
||||
ProgressReporter.STATE_DOING = 1;
|
||||
ProgressReporter.STATE_FINISHED = 2;
|
||||
|
||||
ProgressReporter.prototype = {
|
||||
getProgress: function () {
|
||||
return this._progress;
|
||||
},
|
||||
getState: function () {
|
||||
return this._state;
|
||||
},
|
||||
setAction: function (action) {
|
||||
this._action = action;
|
||||
this._reportProgress();
|
||||
},
|
||||
getAction: function () {
|
||||
switch (this._state) {
|
||||
case ProgressReporter.STATE_WAITING:
|
||||
return "Waiting for preceding tasks to finish...";
|
||||
case ProgressReporter.STATE_DOING:
|
||||
return this._action;
|
||||
case ProgressReporter.STATE_FINISHED:
|
||||
return "Finished.";
|
||||
default:
|
||||
throw "Broken state";
|
||||
}
|
||||
},
|
||||
addListener: function (callback) {
|
||||
this._observers.push(callback);
|
||||
},
|
||||
addSubreporter: function (expectedDuration) {
|
||||
this._subreporterExpectedDurationsSum += expectedDuration;
|
||||
var subreporter = new ProgressReporter();
|
||||
var self = this;
|
||||
subreporter.addListener(function (progress) {
|
||||
self._recalculateProgressFromSubreporters();
|
||||
self._recalculateStateAndActionFromSubreporters();
|
||||
self._reportProgress();
|
||||
});
|
||||
this._subreporters.push({ expectedDuration: expectedDuration, reporter: subreporter });
|
||||
return subreporter;
|
||||
},
|
||||
addSubreporters: function (expectedDurations) {
|
||||
var reporters = {};
|
||||
for (var key in expectedDurations) {
|
||||
reporters[key] = this.addSubreporter(expectedDurations[key]);
|
||||
}
|
||||
return reporters;
|
||||
},
|
||||
begin: function (action) {
|
||||
this._startTime = Date.now();
|
||||
this._state = ProgressReporter.STATE_DOING;
|
||||
this._action = action;
|
||||
this._reportProgress();
|
||||
},
|
||||
setProgress: function (progress) {
|
||||
if (this._subreporters.length > 0)
|
||||
throw "Can't call setProgress on a progress reporter with subreporters";
|
||||
if (progress != this._progress &&
|
||||
(progress == 1 ||
|
||||
(isNaN(progress) != isNaN(this._progress)) ||
|
||||
(progress - this._progress >= 0.01))) {
|
||||
this._progress = progress;
|
||||
if (progress == 1)
|
||||
this._transitionToFinished();
|
||||
this._reportProgress();
|
||||
}
|
||||
},
|
||||
finish: function () {
|
||||
this.setProgress(1);
|
||||
},
|
||||
_recalculateProgressFromSubreporters: function () {
|
||||
if (this._subreporters.length == 0)
|
||||
throw "Can't _recalculateProgressFromSubreporters on a progress reporter without any subreporters";
|
||||
this._progress = 0;
|
||||
for (var i = 0; i < this._subreporters.length; i++) {
|
||||
var expectedDuration = this._subreporters[i].expectedDuration;
|
||||
var reporter = this._subreporters[i].reporter;
|
||||
this._progress += reporter.getProgress() * expectedDuration / this._subreporterExpectedDurationsSum;
|
||||
}
|
||||
},
|
||||
_recalculateStateAndActionFromSubreporters: function () {
|
||||
if (this._subreporters.length == 0)
|
||||
throw "Can't _recalculateStateAndActionFromSubreporters on a progress reporter without any subreporters";
|
||||
var actions = [];
|
||||
var allWaiting = true;
|
||||
var allFinished = true;
|
||||
for (var i = 0; i < this._subreporters.length; i++) {
|
||||
var expectedDuration = this._subreporters[i].expectedDuration;
|
||||
var reporter = this._subreporters[i].reporter;
|
||||
var state = reporter.getState();
|
||||
if (state != ProgressReporter.STATE_WAITING)
|
||||
allWaiting = false;
|
||||
if (state != ProgressReporter.STATE_FINISHED)
|
||||
allFinished = false;
|
||||
if (state == ProgressReporter.STATE_DOING)
|
||||
actions.push(reporter.getAction());
|
||||
}
|
||||
if (allFinished) {
|
||||
this._transitionToFinished();
|
||||
} else if (!allWaiting) {
|
||||
this._state = ProgressReporter.STATE_DOING;
|
||||
if (actions.length == 0) {
|
||||
this._action = "About to start next task..."
|
||||
} else {
|
||||
this._action = actions.join("\n");
|
||||
}
|
||||
}
|
||||
},
|
||||
_transitionToFinished: function () {
|
||||
this._state = ProgressReporter.STATE_FINISHED;
|
||||
|
||||
if (gDebugExpectedDurations) {
|
||||
this._realDuration = Date.now() - this._startTime;
|
||||
if (this._subreporters.length) {
|
||||
for (var i = 0; i < this._subreporters.length; i++) {
|
||||
var expectedDuration = this._subreporters[i].expectedDuration;
|
||||
var reporter = this._subreporters[i].reporter;
|
||||
var realDuration = reporter._realDuration;
|
||||
dump("For reporter with expectedDuration " + expectedDuration + ", real duration was " + realDuration + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_reportProgress: function () {
|
||||
for (var i = 0; i < this._observers.length; i++) {
|
||||
this._observers[i](this);
|
||||
}
|
||||
},
|
||||
};
|
@ -1,244 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gInstanceUID;
|
||||
var gParsedQS;
|
||||
var gHideSourceLinks;
|
||||
|
||||
function getParam(key) {
|
||||
if (gParsedQS)
|
||||
return gParsedQS[key];
|
||||
|
||||
var query = window.location.search.substring(1);
|
||||
gParsedQS = {};
|
||||
|
||||
query.split("&").forEach(function (pair) {
|
||||
pair = pair.split("=");
|
||||
gParsedQS[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
|
||||
});
|
||||
|
||||
return gParsedQS[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the parent window with a status
|
||||
* update.
|
||||
*
|
||||
* @param string status
|
||||
* Status to send to the parent page:
|
||||
* - loaded, when page is loaded.
|
||||
* - displaysource, when user wants to display source
|
||||
* @param object data (optional)
|
||||
* Additional data to send to the parent page.
|
||||
*/
|
||||
function notifyParent(status, data={}) {
|
||||
if (!gInstanceUID) {
|
||||
gInstanceUID = getParam("uid");
|
||||
}
|
||||
|
||||
window.parent.postMessage({
|
||||
uid: gInstanceUID,
|
||||
status: status,
|
||||
data: data
|
||||
}, "*");
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener for incoming messages from the parent
|
||||
* page. All incoming messages must be stringified
|
||||
* JSON objects to be compatible with Cleopatra's
|
||||
* format:
|
||||
*
|
||||
* {
|
||||
* task: string,
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* This listener recognizes two tasks: onStarted and
|
||||
* onStopped.
|
||||
*
|
||||
* @param object event
|
||||
* PostMessage event object.
|
||||
*/
|
||||
function onParentMessage(event) {
|
||||
var start = document.getElementById("startWrapper");
|
||||
var stop = document.getElementById("stopWrapper");
|
||||
var profilerMessage = document.getElementById("profilerMessage");
|
||||
var msg = JSON.parse(event.data);
|
||||
|
||||
if (msg.task !== "receiveProfileData" && !msg.isCurrent) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.task) {
|
||||
case "onStarted":
|
||||
start.style.display = "none";
|
||||
start.querySelector("button").removeAttribute("disabled");
|
||||
stop.style.display = "inline";
|
||||
break;
|
||||
case "onStopped":
|
||||
stop.style.display = "none";
|
||||
stop.querySelector("button").removeAttribute("disabled");
|
||||
start.style.display = "inline";
|
||||
break;
|
||||
case "receiveProfileData":
|
||||
loadProfile(JSON.stringify(msg.rawProfile));
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", onParentMessage);
|
||||
|
||||
/**
|
||||
* Main entry point. This function initializes Cleopatra
|
||||
* in the light mode and creates all the UI we need.
|
||||
*/
|
||||
function initUI() {
|
||||
gHideSourceLinks = getParam("ext") === "true";
|
||||
gFileList = { profileParsingFinished: function () {} };
|
||||
gInfoBar = { display: function () {} };
|
||||
|
||||
var container = document.createElement("div");
|
||||
container.id = "ui";
|
||||
|
||||
gMainArea = document.createElement("div");
|
||||
gMainArea.id = "mainarea";
|
||||
|
||||
container.appendChild(gMainArea);
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modified copy of Cleopatra's enterFinishedProfileUI.
|
||||
* By overriding the function we don't need to modify ui.js which helps
|
||||
* with updating from upstream.
|
||||
*/
|
||||
function enterFinishedProfileUI() {
|
||||
var cover = document.createElement("div");
|
||||
cover.className = "finishedProfilePaneBackgroundCover";
|
||||
|
||||
var pane = document.createElement("table");
|
||||
var rowIndex = 0;
|
||||
var currRow;
|
||||
|
||||
pane.style.width = "100%";
|
||||
pane.style.height = "100%";
|
||||
pane.border = "0";
|
||||
pane.cellPadding = "0";
|
||||
pane.cellSpacing = "0";
|
||||
pane.borderCollapse = "collapse";
|
||||
pane.className = "finishedProfilePane";
|
||||
|
||||
gBreadcrumbTrail = new BreadcrumbTrail();
|
||||
currRow = pane.insertRow(rowIndex++);
|
||||
currRow.insertCell(0).appendChild(gBreadcrumbTrail.getContainer());
|
||||
|
||||
gHistogramView = new HistogramView();
|
||||
currRow = pane.insertRow(rowIndex++);
|
||||
currRow.insertCell(0).appendChild(gHistogramView.getContainer());
|
||||
|
||||
if (gMeta && gMeta.videoCapture) {
|
||||
gVideoPane = new VideoPane(gMeta.videoCapture);
|
||||
gVideoPane.onTimeChange(videoPaneTimeChange);
|
||||
currRow = pane.insertRow(rowIndex++);
|
||||
currRow.insertCell(0).appendChild(gVideoPane.getContainer());
|
||||
}
|
||||
|
||||
var tree = document.createElement("div");
|
||||
tree.className = "treeContainer";
|
||||
tree.style.width = "100%";
|
||||
tree.style.height = "100%";
|
||||
|
||||
gTreeManager = new ProfileTreeManager();
|
||||
gTreeManager.treeView.setColumns([
|
||||
{ name: "sampleCount", title: gStrings["Running Time"] },
|
||||
{ name: "selfSampleCount", title: gStrings["Self"] },
|
||||
{ name: "resource", title: "" }
|
||||
]);
|
||||
|
||||
currRow = pane.insertRow(rowIndex++);
|
||||
currRow.style.height = "100%";
|
||||
|
||||
var cell = currRow.insertCell(0);
|
||||
cell.appendChild(tree);
|
||||
tree.appendChild(gTreeManager.getContainer());
|
||||
|
||||
gPluginView = new PluginView();
|
||||
tree.appendChild(gPluginView.getContainer());
|
||||
|
||||
gMainArea.appendChild(cover);
|
||||
gMainArea.appendChild(pane);
|
||||
|
||||
var currentBreadcrumb = gSampleFilters;
|
||||
gBreadcrumbTrail.add({
|
||||
title: gStrings["Complete Profile"],
|
||||
enterCallback: function () {
|
||||
gSampleFilters = [];
|
||||
filtersChanged();
|
||||
}
|
||||
});
|
||||
|
||||
if (currentBreadcrumb == null || currentBreadcrumb.length == 0) {
|
||||
gTreeManager.restoreSerializedSelectionSnapshot(gRestoreSelection);
|
||||
viewOptionsChanged();
|
||||
}
|
||||
|
||||
for (var i = 0; i < currentBreadcrumb.length; i++) {
|
||||
var filter = currentBreadcrumb[i];
|
||||
var forceSelection = null;
|
||||
if (gRestoreSelection != null && i == currentBreadcrumb.length - 1) {
|
||||
forceSelection = gRestoreSelection;
|
||||
}
|
||||
switch (filter.type) {
|
||||
case "FocusedFrameSampleFilter":
|
||||
focusOnSymbol(filter.name, filter.symbolName);
|
||||
gBreadcrumbTrail.enterLastItem(forceSelection);
|
||||
case "FocusedCallstackPrefixSampleFilter":
|
||||
focusOnCallstack(filter.focusedCallstack, filter.name, false);
|
||||
gBreadcrumbTrail.enterLastItem(forceSelection);
|
||||
case "FocusedCallstackPostfixSampleFilter":
|
||||
focusOnCallstack(filter.focusedCallstack, filter.name, true);
|
||||
gBreadcrumbTrail.enterLastItem(forceSelection);
|
||||
case "RangeSampleFilter":
|
||||
gHistogramView.selectRange(filter.start, filter.end);
|
||||
gBreadcrumbTrail.enterLastItem(forceSelection);
|
||||
}
|
||||
}
|
||||
|
||||
// Show platform data?
|
||||
if (getParam("spd") !== "true")
|
||||
toggleJavascriptOnly();
|
||||
}
|
||||
|
||||
function enterProgressUI() {
|
||||
var pane = document.createElement("div");
|
||||
var label = document.createElement("a");
|
||||
var bar = document.createElement("progress");
|
||||
var string = gStrings.getStr("profiler.loading");
|
||||
|
||||
pane.className = "profileProgressPane";
|
||||
pane.appendChild(label);
|
||||
pane.appendChild(bar);
|
||||
|
||||
var reporter = new ProgressReporter();
|
||||
reporter.addListener(function (rep) {
|
||||
var progress = rep.getProgress();
|
||||
|
||||
if (label.textContent !== string) {
|
||||
label.textContent = string;
|
||||
}
|
||||
|
||||
if (isNaN(progress)) {
|
||||
bar.removeAttribute("value");
|
||||
} else {
|
||||
bar.value = progress;
|
||||
}
|
||||
});
|
||||
|
||||
gMainArea.appendChild(pane);
|
||||
Parser.updateLogSetting();
|
||||
|
||||
return reporter;
|
||||
}
|
@ -1,271 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
Array.prototype.clone = function() { return this.slice(0); }
|
||||
|
||||
function makeSample(frames, extraInfo, lines) {
|
||||
return {
|
||||
frames: frames,
|
||||
extraInfo: extraInfo,
|
||||
lines: lines
|
||||
};
|
||||
}
|
||||
|
||||
function cloneSample(sample) {
|
||||
return makeSample(sample.frames.clone(), sample.extraInfo, sample.lines.clone());
|
||||
}
|
||||
|
||||
function bucketsBySplittingArray(array, maxItemsPerBucket) {
|
||||
var buckets = [];
|
||||
while (buckets.length * maxItemsPerBucket < array.length) {
|
||||
buckets.push(array.slice(buckets.length * maxItemsPerBucket,
|
||||
(buckets.length + 1) * maxItemsPerBucket));
|
||||
}
|
||||
return buckets;
|
||||
}
|
||||
|
||||
var gParserWorker = new Worker("profiler/cleopatra/js/parserWorker.js");
|
||||
gParserWorker.nextRequestID = 0;
|
||||
|
||||
function WorkerRequest(worker) {
|
||||
var self = this;
|
||||
this._eventListeners = {};
|
||||
var requestID = worker.nextRequestID++;
|
||||
this._requestID = requestID;
|
||||
this._worker = worker;
|
||||
this._totalReporter = new ProgressReporter();
|
||||
this._totalReporter.addListener(function (reporter) {
|
||||
self._fireEvent("progress", reporter.getProgress(), reporter.getAction());
|
||||
})
|
||||
this._sendChunkReporter = this._totalReporter.addSubreporter(500);
|
||||
this._executeReporter = this._totalReporter.addSubreporter(3000);
|
||||
this._receiveChunkReporter = this._totalReporter.addSubreporter(100);
|
||||
this._totalReporter.begin("Processing task in worker...");
|
||||
var partialResult = null;
|
||||
function onMessageFromWorker(msg) {
|
||||
pendingMessages.push(msg);
|
||||
scheduleMessageProcessing();
|
||||
}
|
||||
function processMessage(msg) {
|
||||
var startTime = Date.now();
|
||||
var data = msg.data;
|
||||
var readTime = Date.now() - startTime;
|
||||
|
||||
if (data.requestID == requestID || !data.requestID) {
|
||||
switch(data.type) {
|
||||
case "error":
|
||||
self._sendChunkReporter.setAction("Error in worker: " + data.error);
|
||||
self._executeReporter.setAction("Error in worker: " + data.error);
|
||||
self._receiveChunkReporter.setAction("Error in worker: " + data.error);
|
||||
self._totalReporter.setAction("Error in worker: " + data.error);
|
||||
PROFILERERROR("Error in worker: " + data.error);
|
||||
self._fireEvent("error", data.error);
|
||||
break;
|
||||
case "progress":
|
||||
self._executeReporter.setProgress(data.progress);
|
||||
break;
|
||||
case "finished":
|
||||
self._executeReporter.finish();
|
||||
self._receiveChunkReporter.begin("Receiving data from worker...");
|
||||
self._receiveChunkReporter.finish();
|
||||
self._fireEvent("finished", data.result);
|
||||
worker.removeEventListener("message", onMessageFromWorker);
|
||||
break;
|
||||
case "finishedStart":
|
||||
partialResult = null;
|
||||
self._totalReceiveChunks = data.numChunks;
|
||||
self._gotReceiveChunks = 0;
|
||||
self._executeReporter.finish();
|
||||
self._receiveChunkReporter.begin("Receiving data from worker...");
|
||||
break;
|
||||
case "finishedChunk":
|
||||
partialResult = partialResult ? partialResult.concat(data.chunk) : data.chunk;
|
||||
var chunkIndex = self._gotReceiveChunks++;
|
||||
self._receiveChunkReporter.setProgress((chunkIndex + 1) / self._totalReceiveChunks);
|
||||
break;
|
||||
case "finishedEnd":
|
||||
self._receiveChunkReporter.finish();
|
||||
self._fireEvent("finished", partialResult);
|
||||
worker.removeEventListener("message", onMessageFromWorker);
|
||||
break;
|
||||
}
|
||||
// dump log if present
|
||||
if (data.log) {
|
||||
for (var line in data.log) {
|
||||
PROFILERLOG(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var pendingMessages = [];
|
||||
var messageProcessingTimer = 0;
|
||||
function processMessages() {
|
||||
messageProcessingTimer = 0;
|
||||
processMessage(pendingMessages.shift());
|
||||
if (pendingMessages.length)
|
||||
scheduleMessageProcessing();
|
||||
}
|
||||
function scheduleMessageProcessing() {
|
||||
if (messageProcessingTimer)
|
||||
return;
|
||||
messageProcessingTimer = setTimeout(processMessages, 10);
|
||||
}
|
||||
worker.addEventListener("message", onMessageFromWorker);
|
||||
}
|
||||
|
||||
WorkerRequest.prototype = {
|
||||
send: function WorkerRequest_send(task, taskData) {
|
||||
this._sendChunkReporter.begin("Sending data to worker...");
|
||||
var startTime = Date.now();
|
||||
this._worker.postMessage({
|
||||
requestID: this._requestID,
|
||||
task: task,
|
||||
taskData: taskData
|
||||
});
|
||||
var postTime = Date.now() - startTime;
|
||||
this._sendChunkReporter.finish();
|
||||
this._executeReporter.begin("Processing worker request...");
|
||||
},
|
||||
sendInChunks: function WorkerRequest_sendInChunks(task, taskData, params, maxChunkSize) {
|
||||
this._sendChunkReporter.begin("Sending data to worker...");
|
||||
var self = this;
|
||||
var chunks = bucketsBySplittingArray(taskData, maxChunkSize);
|
||||
var pendingMessages = [
|
||||
{
|
||||
requestID: this._requestID,
|
||||
task: "chunkedStart",
|
||||
numChunks: chunks.length
|
||||
}
|
||||
].concat(chunks.map(function (chunk) {
|
||||
return {
|
||||
requestID: self._requestID,
|
||||
task: "chunkedChunk",
|
||||
chunk: chunk
|
||||
};
|
||||
})).concat([
|
||||
{
|
||||
requestID: this._requestID,
|
||||
task: "chunkedEnd"
|
||||
},
|
||||
{
|
||||
requestID: this._requestID,
|
||||
params: params,
|
||||
task: task
|
||||
},
|
||||
]);
|
||||
var totalMessages = pendingMessages.length;
|
||||
var numSentMessages = 0;
|
||||
function postMessage(msg) {
|
||||
var msgIndex = numSentMessages++;
|
||||
var startTime = Date.now();
|
||||
self._worker.postMessage(msg);
|
||||
var postTime = Date.now() - startTime;
|
||||
self._sendChunkReporter.setProgress((msgIndex + 1) / totalMessages);
|
||||
}
|
||||
var messagePostingTimer = 0;
|
||||
function postMessages() {
|
||||
messagePostingTimer = 0;
|
||||
postMessage(pendingMessages.shift());
|
||||
if (pendingMessages.length) {
|
||||
scheduleMessagePosting();
|
||||
} else {
|
||||
self._sendChunkReporter.finish();
|
||||
self._executeReporter.begin("Processing worker request...");
|
||||
}
|
||||
}
|
||||
function scheduleMessagePosting() {
|
||||
if (messagePostingTimer)
|
||||
return;
|
||||
messagePostingTimer = setTimeout(postMessages, 10);
|
||||
}
|
||||
scheduleMessagePosting();
|
||||
},
|
||||
|
||||
// TODO: share code with TreeView
|
||||
addEventListener: function WorkerRequest_addEventListener(eventName, callbackFunction) {
|
||||
if (!(eventName in this._eventListeners))
|
||||
this._eventListeners[eventName] = [];
|
||||
if (this._eventListeners[eventName].indexOf(callbackFunction) != -1)
|
||||
return;
|
||||
this._eventListeners[eventName].push(callbackFunction);
|
||||
},
|
||||
removeEventListener: function WorkerRequest_removeEventListener(eventName, callbackFunction) {
|
||||
if (!(eventName in this._eventListeners))
|
||||
return;
|
||||
var index = this._eventListeners[eventName].indexOf(callbackFunction);
|
||||
if (index == -1)
|
||||
return;
|
||||
this._eventListeners[eventName].splice(index, 1);
|
||||
},
|
||||
_fireEvent: function WorkerRequest__fireEvent(eventName, eventObject, p1) {
|
||||
if (!(eventName in this._eventListeners))
|
||||
return;
|
||||
this._eventListeners[eventName].forEach(function (callbackFunction) {
|
||||
callbackFunction(eventObject, p1);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
var Parser = {
|
||||
parse: function Parser_parse(data, params) {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.sendInChunks("parseRawProfile", data, params, 3000000);
|
||||
return request;
|
||||
},
|
||||
|
||||
updateFilters: function Parser_updateFilters(filters) {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.send("updateFilters", {
|
||||
filters: filters,
|
||||
profileID: 0
|
||||
});
|
||||
return request;
|
||||
},
|
||||
|
||||
updateViewOptions: function Parser_updateViewOptions(options) {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.send("updateViewOptions", {
|
||||
options: options,
|
||||
profileID: 0
|
||||
});
|
||||
return request;
|
||||
},
|
||||
|
||||
getSerializedProfile: function Parser_getSerializedProfile(complete, callback) {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.send("getSerializedProfile", {
|
||||
profileID: 0,
|
||||
complete: complete
|
||||
});
|
||||
request.addEventListener("finished", callback);
|
||||
},
|
||||
|
||||
calculateHistogramData: function Parser_calculateHistogramData() {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.send("calculateHistogramData", {
|
||||
profileID: 0
|
||||
});
|
||||
return request;
|
||||
},
|
||||
|
||||
calculateDiagnosticItems: function Parser_calculateDiagnosticItems(meta) {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.send("calculateDiagnosticItems", {
|
||||
profileID: 0,
|
||||
meta: meta
|
||||
});
|
||||
return request;
|
||||
},
|
||||
|
||||
updateLogSetting: function Parser_updateLogSetting() {
|
||||
var request = new WorkerRequest(gParserWorker);
|
||||
request.send("initWorker", {
|
||||
debugLog: gDebugLog,
|
||||
debugTrace: gDebugTrace,
|
||||
});
|
||||
return request;
|
||||
},
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,29 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
const { L10N_BUNDLE } = require("devtools/profiler/consts");
|
||||
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
var L10N = new ViewHelpers.L10N(L10N_BUNDLE);
|
||||
|
||||
/**
|
||||
* Shortcuts for the L10N helper functions. Used in Cleopatra.
|
||||
*/
|
||||
var gStrings = {
|
||||
// This strings are here so that Cleopatra code could use a simple object
|
||||
// lookup. This makes it easier to merge upstream changes.
|
||||
"Complete Profile": L10N.getStr("profiler.completeProfile"),
|
||||
"Sample Range": L10N.getStr("profiler.sampleRange"),
|
||||
"Running Time": L10N.getStr("profiler.runningTime"),
|
||||
"Self": L10N.getStr("profiler.self"),
|
||||
"Symbol Name": L10N.getStr("profiler.symbolName"),
|
||||
|
||||
getStr: function (name) {
|
||||
return L10N.getStr(name);
|
||||
},
|
||||
|
||||
getFormatStr: function (name, params) {
|
||||
return L10N.getFormatStr(name, params);
|
||||
}
|
||||
};
|
@ -1,702 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var kMaxChunkDuration = 30; // ms
|
||||
|
||||
function escapeHTML(html) {
|
||||
var pre = document.createElementNS("http://www.w3.org/1999/xhtml", "pre");
|
||||
var text = document.createTextNode(html);
|
||||
pre.appendChild(text);
|
||||
return pre.innerHTML;
|
||||
}
|
||||
|
||||
RegExp.escape = function(text) {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
}
|
||||
|
||||
var requestAnimationFrame = window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function(callback, element) {
|
||||
return window.setTimeout(callback, 1000 / 60);
|
||||
};
|
||||
|
||||
var cancelAnimationFrame = window.webkitCancelAnimationFrame ||
|
||||
window.mozCancelAnimationFrame ||
|
||||
window.oCancelAnimationFrame ||
|
||||
window.msCancelAnimationFrame ||
|
||||
function(req) {
|
||||
window.clearTimeout(req);
|
||||
};
|
||||
|
||||
function TreeView() {
|
||||
this._eventListeners = {};
|
||||
this._pendingActions = [];
|
||||
this._pendingActionsProcessingCallback = null;
|
||||
|
||||
this._container = document.createElement("div");
|
||||
this._container.className = "treeViewContainer";
|
||||
this._container.setAttribute("tabindex", "0"); // make it focusable
|
||||
|
||||
this._header = document.createElement("ul");
|
||||
this._header.className = "treeHeader";
|
||||
this._container.appendChild(this._header);
|
||||
|
||||
this._verticalScrollbox = document.createElement("div");
|
||||
this._verticalScrollbox.className = "treeViewVerticalScrollbox";
|
||||
this._container.appendChild(this._verticalScrollbox);
|
||||
|
||||
this._leftColumnBackground = document.createElement("div");
|
||||
this._leftColumnBackground.className = "leftColumnBackground";
|
||||
this._verticalScrollbox.appendChild(this._leftColumnBackground);
|
||||
|
||||
this._horizontalScrollbox = document.createElement("div");
|
||||
this._horizontalScrollbox.className = "treeViewHorizontalScrollbox";
|
||||
this._verticalScrollbox.appendChild(this._horizontalScrollbox);
|
||||
|
||||
this._styleElement = document.createElement("style");
|
||||
this._styleElement.setAttribute("type", "text/css");
|
||||
this._container.appendChild(this._styleElement);
|
||||
|
||||
this._contextMenu = document.createElement("menu");
|
||||
this._contextMenu.setAttribute("type", "context");
|
||||
this._contextMenu.id = "contextMenuForTreeView" + TreeView.instanceCounter++;
|
||||
this._container.appendChild(this._contextMenu);
|
||||
|
||||
this._busyCover = document.createElement("div");
|
||||
this._busyCover.className = "busyCover";
|
||||
this._container.appendChild(this._busyCover);
|
||||
this._abortToggleAll = false;
|
||||
this.initSelection = true;
|
||||
|
||||
var self = this;
|
||||
this._container.onkeydown = function (e) {
|
||||
self._onkeypress(e);
|
||||
};
|
||||
this._container.onkeypress = function (e) {
|
||||
// on key down gives us '8' and mapping shift+8='*' may not be portable.
|
||||
if (String.fromCharCode(e.charCode) == '*')
|
||||
self._onkeypress(e);
|
||||
};
|
||||
this._container.onclick = function (e) {
|
||||
self._onclick(e);
|
||||
};
|
||||
this._verticalScrollbox.addEventListener("contextmenu", function(event) {
|
||||
self._populateContextMenu(event);
|
||||
}, true);
|
||||
this._setUpScrolling();
|
||||
};
|
||||
TreeView.instanceCounter = 0;
|
||||
|
||||
TreeView.prototype = {
|
||||
getContainer: function TreeView_getContainer() {
|
||||
return this._container;
|
||||
},
|
||||
setColumns: function TreeView_setColumns(columns) {
|
||||
this._header.innerHTML = "";
|
||||
for (var i = 0; i < columns.length; i++) {
|
||||
var li = document.createElement("li");
|
||||
li.className = "treeColumnHeader treeColumnHeader" + i;
|
||||
li.id = columns[i].name + "Header";
|
||||
li.textContent = columns[i].title;
|
||||
this._header.appendChild(li);
|
||||
}
|
||||
},
|
||||
dataIsOutdated: function TreeView_dataIsOutdated() {
|
||||
this._busyCover.classList.add("busy");
|
||||
},
|
||||
display: function TreeView_display(data, resources, filterByName) {
|
||||
this._busyCover.classList.remove("busy");
|
||||
this._filterByName = filterByName;
|
||||
this._resources = resources;
|
||||
this._addResourceIconStyles();
|
||||
this._filterByNameReg = null; // lazy init
|
||||
if (this._filterByName === "")
|
||||
this._filterByName = null;
|
||||
this._horizontalScrollbox.innerHTML = "";
|
||||
this._horizontalScrollbox.data = data[0].getData();
|
||||
if (this._pendingActionsProcessingCallback) {
|
||||
cancelAnimationFrame(this._pendingActionsProcessingCallback);
|
||||
this._pendingActionsProcessingCallback = 0;
|
||||
}
|
||||
this._pendingActions = [];
|
||||
|
||||
this._pendingActions.push({
|
||||
parentElement: this._horizontalScrollbox,
|
||||
parentNode: null,
|
||||
data: data[0].getData()
|
||||
});
|
||||
this._processPendingActionsChunk();
|
||||
changeFocus(this._container);
|
||||
},
|
||||
// Provide a snapshot of the reverse selection to restore with 'invert callback'
|
||||
getReverseSelectionSnapshot: function TreeView__getReverseSelectionSnapshot(isJavascriptOnly) {
|
||||
var snapshot = [];
|
||||
|
||||
if (!this._selectedNode) {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
var curr = this._selectedNode.data;
|
||||
|
||||
while(curr) {
|
||||
if (isJavascriptOnly && curr.isJSFrame || !isJavascriptOnly) {
|
||||
snapshot.push(curr.name);
|
||||
//dump(JSON.stringify(curr.name) + "\n");
|
||||
}
|
||||
if (curr.treeChildren && curr.treeChildren.length >= 1) {
|
||||
curr = curr.treeChildren[0].getData();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot.reverse();
|
||||
},
|
||||
// Provide a snapshot of the current selection to restore
|
||||
getSelectionSnapshot: function TreeView__getSelectionSnapshot(isJavascriptOnly) {
|
||||
var snapshot = [];
|
||||
var curr = this._selectedNode;
|
||||
|
||||
while(curr) {
|
||||
if (isJavascriptOnly && curr.data.isJSFrame || !isJavascriptOnly) {
|
||||
snapshot.push(curr.data.name);
|
||||
//dump(JSON.stringify(curr.data.name) + "\n");
|
||||
}
|
||||
curr = curr.treeParent;
|
||||
}
|
||||
|
||||
return snapshot.reverse();
|
||||
},
|
||||
setSelection: function TreeView_setSelection(frames) {
|
||||
this.restoreSelectionSnapshot(frames, false);
|
||||
},
|
||||
// Take a selection snapshot and restore the selection
|
||||
restoreSelectionSnapshot: function TreeView_restoreSelectionSnapshot(snapshot, allowNonContiguous) {
|
||||
var currNode = this._horizontalScrollbox.firstChild;
|
||||
if (currNode.data.name == snapshot[0] || snapshot[0] == "(total)") {
|
||||
snapshot.shift();
|
||||
}
|
||||
//dump("len: " + snapshot.length + "\n");
|
||||
next_level: while (currNode && snapshot.length > 0) {
|
||||
this._toggle(currNode, false, true);
|
||||
this._syncProcessPendingActionProcessing();
|
||||
for (var i = 0; i < currNode.treeChildren.length; i++) {
|
||||
if (currNode.treeChildren[i].data.name == snapshot[0]) {
|
||||
snapshot.shift();
|
||||
this._toggle(currNode, false, true);
|
||||
currNode = currNode.treeChildren[i];
|
||||
continue next_level;
|
||||
}
|
||||
}
|
||||
if (allowNonContiguous) {
|
||||
// We need to do a Breadth-first search to find a match
|
||||
var pendingSearch = [currNode.data];
|
||||
while (pendingSearch.length > 0) {
|
||||
var node = pendingSearch.shift();
|
||||
if (!node.treeChildren)
|
||||
continue;
|
||||
for (var i = 0; i < node.treeChildren.length; i++) {
|
||||
var childNode = node.treeChildren[i].getData();
|
||||
if (childNode.name == snapshot[0]) {
|
||||
//dump("found: " + childNode.name + "\n");
|
||||
snapshot.shift();
|
||||
var nodesToToggle = [childNode];
|
||||
while (nodesToToggle[0].name != currNode.data.name) {
|
||||
nodesToToggle.splice(0, 0, nodesToToggle[0].parent);
|
||||
}
|
||||
var lastToggle = currNode;
|
||||
for (var j = 0; j < nodesToToggle.length; j++) {
|
||||
for (var k = 0; k < lastToggle.treeChildren.length; k++) {
|
||||
if (lastToggle.treeChildren[k].data.name == nodesToToggle[j].name) {
|
||||
//dump("Expend: " + nodesToToggle[j].name + "\n");
|
||||
this._toggle(lastToggle.treeChildren[k], false, true);
|
||||
lastToggle = lastToggle.treeChildren[k];
|
||||
this._syncProcessPendingActionProcessing();
|
||||
}
|
||||
}
|
||||
}
|
||||
currNode = lastToggle;
|
||||
continue next_level;
|
||||
}
|
||||
//dump("pending: " + childNode.name + "\n");
|
||||
pendingSearch.push(childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
break; // Didn't find child node matching
|
||||
}
|
||||
|
||||
if (currNode == this._horizontalScrollbox) {
|
||||
PROFILERERROR("Failed to restore selection, could not find root.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
this._toggle(currNode, true, true);
|
||||
this._select(currNode);
|
||||
},
|
||||
_processPendingActionsChunk: function TreeView__processPendingActionsChunk(isSync) {
|
||||
this._pendingActionsProcessingCallback = 0;
|
||||
|
||||
var startTime = Date.now();
|
||||
var endTime = startTime + kMaxChunkDuration;
|
||||
while ((isSync == true || Date.now() < endTime) && this._pendingActions.length > 0) {
|
||||
this._processOneAction(this._pendingActions.shift());
|
||||
}
|
||||
this._scrollHeightChanged();
|
||||
|
||||
this._schedulePendingActionProcessing();
|
||||
},
|
||||
_schedulePendingActionProcessing: function TreeView__schedulePendingActionProcessing() {
|
||||
if (!this._pendingActionsProcessingCallback && this._pendingActions.length > 0) {
|
||||
var self = this;
|
||||
this._pendingActionsProcessingCallback = requestAnimationFrame(function () {
|
||||
self._processPendingActionsChunk();
|
||||
});
|
||||
}
|
||||
},
|
||||
_syncProcessPendingActionProcessing: function TreeView__syncProcessPendingActionProcessing() {
|
||||
this._processPendingActionsChunk(true);
|
||||
},
|
||||
_processOneAction: function TreeView__processOneAction(action) {
|
||||
var li = this._createTree(action.parentElement, action.parentNode, action.data);
|
||||
if ("allChildrenCollapsedValue" in action) {
|
||||
if (this._abortToggleAll)
|
||||
return;
|
||||
this._toggleAll(li, action.allChildrenCollapsedValue, true);
|
||||
}
|
||||
},
|
||||
addEventListener: function TreeView_addEventListener(eventName, callbackFunction) {
|
||||
if (!(eventName in this._eventListeners))
|
||||
this._eventListeners[eventName] = [];
|
||||
if (this._eventListeners[eventName].indexOf(callbackFunction) != -1)
|
||||
return;
|
||||
this._eventListeners[eventName].push(callbackFunction);
|
||||
},
|
||||
removeEventListener: function TreeView_removeEventListener(eventName, callbackFunction) {
|
||||
if (!(eventName in this._eventListeners))
|
||||
return;
|
||||
var index = this._eventListeners[eventName].indexOf(callbackFunction);
|
||||
if (index == -1)
|
||||
return;
|
||||
this._eventListeners[eventName].splice(index, 1);
|
||||
},
|
||||
_fireEvent: function TreeView__fireEvent(eventName, eventObject) {
|
||||
if (!(eventName in this._eventListeners))
|
||||
return;
|
||||
this._eventListeners[eventName].forEach(function (callbackFunction) {
|
||||
callbackFunction(eventObject);
|
||||
});
|
||||
},
|
||||
_setUpScrolling: function TreeView__setUpScrolling() {
|
||||
var waitingForPaint = false;
|
||||
var accumulatedDeltaX = 0;
|
||||
var accumulatedDeltaY = 0;
|
||||
var self = this;
|
||||
function scrollListener(e) {
|
||||
if (!waitingForPaint) {
|
||||
requestAnimationFrame(function () {
|
||||
self._horizontalScrollbox.scrollLeft += accumulatedDeltaX;
|
||||
self._verticalScrollbox.scrollTop += accumulatedDeltaY;
|
||||
accumulatedDeltaX = 0;
|
||||
accumulatedDeltaY = 0;
|
||||
waitingForPaint = false;
|
||||
});
|
||||
waitingForPaint = true;
|
||||
}
|
||||
if (e.axis == e.HORIZONTAL_AXIS) {
|
||||
accumulatedDeltaX += e.detail;
|
||||
} else {
|
||||
accumulatedDeltaY += e.detail;
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
this._verticalScrollbox.addEventListener("MozMousePixelScroll", scrollListener, false);
|
||||
this._verticalScrollbox.cleanUp = function () {
|
||||
self._verticalScrollbox.removeEventListener("MozMousePixelScroll", scrollListener, false);
|
||||
};
|
||||
},
|
||||
_scrollHeightChanged: function TreeView__scrollHeightChanged() {
|
||||
if (!this._pendingScrollHeightChanged) {
|
||||
var self = this;
|
||||
this._pendingScrollHeightChanged = setTimeout(function() {
|
||||
self._leftColumnBackground.style.height = self._horizontalScrollbox.getBoundingClientRect().height + 'px';
|
||||
self._pendingScrollHeightChanged = null;
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
_createTree: function TreeView__createTree(parentElement, parentNode, data) {
|
||||
var div = document.createElement("div");
|
||||
div.className = "treeViewNode collapsed";
|
||||
var hasChildren = ("children" in data) && (data.children.length > 0);
|
||||
if (!hasChildren)
|
||||
div.classList.add("leaf");
|
||||
var treeLine = document.createElement("div");
|
||||
treeLine.className = "treeLine";
|
||||
treeLine.innerHTML = this._HTMLForFunction(data);
|
||||
div.depth = parentNode ? parentNode.depth + 1 : 0;
|
||||
div.style.marginLeft = div.depth + "em";
|
||||
// When this item is toggled we will expand its children
|
||||
div.pendingExpand = [];
|
||||
div.treeLine = treeLine;
|
||||
div.data = data;
|
||||
// Useful for debugging
|
||||
//this.uniqueID = this.uniqueID || 0;
|
||||
//div.id = "Node" + this.uniqueID++;
|
||||
div.appendChild(treeLine);
|
||||
div.treeChildren = [];
|
||||
div.treeParent = parentNode;
|
||||
if (hasChildren) {
|
||||
for (var i = 0; i < data.children.length; ++i) {
|
||||
div.pendingExpand.push({parentElement: this._horizontalScrollbox, parentNode: div, data: data.children[i].getData() });
|
||||
}
|
||||
}
|
||||
if (parentNode) {
|
||||
parentNode.treeChildren.push(div);
|
||||
}
|
||||
if (parentNode != null) {
|
||||
var nextTo;
|
||||
if (parentNode.treeChildren.length > 1) {
|
||||
nextTo = parentNode.treeChildren[parentNode.treeChildren.length-2].nextSibling;
|
||||
} else {
|
||||
nextTo = parentNode.nextSibling;
|
||||
}
|
||||
parentElement.insertBefore(div, nextTo);
|
||||
} else {
|
||||
parentElement.appendChild(div);
|
||||
}
|
||||
return div;
|
||||
},
|
||||
_addResourceIconStyles: function TreeView__addResourceIconStyles() {
|
||||
var styles = [];
|
||||
for (var resourceName in this._resources) {
|
||||
var resource = this._resources[resourceName];
|
||||
if (resource.icon) {
|
||||
styles.push('.resourceIcon[data-resource="' + resourceName + '"] { background-image: url("' + resource.icon + '"); }');
|
||||
}
|
||||
}
|
||||
this._styleElement.textContent = styles.join("\n");
|
||||
},
|
||||
_populateContextMenu: function TreeView__populateContextMenu(event) {
|
||||
this._verticalScrollbox.setAttribute("contextmenu", "");
|
||||
|
||||
var target = event.target;
|
||||
if (target.classList.contains("expandCollapseButton") ||
|
||||
target.classList.contains("focusCallstackButton"))
|
||||
return;
|
||||
|
||||
var li = this._getParentTreeViewNode(target);
|
||||
if (!li)
|
||||
return;
|
||||
|
||||
this._select(li);
|
||||
|
||||
this._contextMenu.innerHTML = "";
|
||||
|
||||
var self = this;
|
||||
this._contextMenuForFunction(li.data).forEach(function (menuItem) {
|
||||
var menuItemNode = document.createElement("menuitem");
|
||||
menuItemNode.onclick = (function (menuItem) {
|
||||
return function() {
|
||||
self._contextMenuClick(li.data, menuItem);
|
||||
};
|
||||
})(menuItem);
|
||||
menuItemNode.label = menuItem;
|
||||
self._contextMenu.appendChild(menuItemNode);
|
||||
});
|
||||
|
||||
this._verticalScrollbox.setAttribute("contextmenu", this._contextMenu.id);
|
||||
},
|
||||
_contextMenuClick: function TreeView__contextMenuClick(node, menuItem) {
|
||||
this._fireEvent("contextMenuClick", { node: node, menuItem: menuItem });
|
||||
},
|
||||
_contextMenuForFunction: function TreeView__contextMenuForFunction(node) {
|
||||
// TODO move me outside tree.js
|
||||
var menu = [];
|
||||
if (node.library && (
|
||||
node.library.toLowerCase() == "lib_xul" ||
|
||||
node.library.toLowerCase() == "lib_xul.dll"
|
||||
)) {
|
||||
menu.push("View Source");
|
||||
}
|
||||
if (node.isJSFrame && node.scriptLocation) {
|
||||
menu.push("View JS Source");
|
||||
}
|
||||
menu.push("Focus Frame");
|
||||
menu.push("Focus Callstack");
|
||||
menu.push("Google Search");
|
||||
menu.push("Plugin View: Pie");
|
||||
menu.push("Plugin View: Tree");
|
||||
return menu;
|
||||
},
|
||||
_HTMLForFunction: function TreeView__HTMLForFunction(node) {
|
||||
var nodeName = escapeHTML(node.name);
|
||||
var resource = this._resources[node.library] || {};
|
||||
var libName = escapeHTML(resource.name || "");
|
||||
if (this._filterByName) {
|
||||
if (!this._filterByNameReg) {
|
||||
this._filterByName = RegExp.escape(this._filterByName);
|
||||
this._filterByNameReg = new RegExp("(" + this._filterByName + ")","gi");
|
||||
}
|
||||
nodeName = nodeName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>");
|
||||
libName = libName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>");
|
||||
}
|
||||
var samplePercentage;
|
||||
if (isNaN(node.ratio)) {
|
||||
samplePercentage = "";
|
||||
} else {
|
||||
samplePercentage = (100 * node.ratio).toFixed(1) + "%";
|
||||
}
|
||||
return '<input type="button" value="Expand / Collapse" class="expandCollapseButton" tabindex="-1"> ' +
|
||||
'<span class="sampleCount">' + node.counter + '</span> ' +
|
||||
'<span class="samplePercentage">' + samplePercentage + '</span> ' +
|
||||
'<span class="selfSampleCount">' + node.selfCounter + '</span> ' +
|
||||
'<span class="resourceIcon" data-resource="' + node.library + '"></span> ' +
|
||||
'<span class="functionName">' + nodeName + '</span>' +
|
||||
'<span class="libraryName">' + libName + '</span>' +
|
||||
((nodeName === '(total)' || gHideSourceLinks) ? '' :
|
||||
'<input type="button" value="Focus Callstack" title="Focus Callstack" class="focusCallstackButton" tabindex="-1">');
|
||||
},
|
||||
_resolveChildren: function TreeView__resolveChildren(div, childrenCollapsedValue) {
|
||||
while (div.pendingExpand != null && div.pendingExpand.length > 0) {
|
||||
var pendingExpand = div.pendingExpand.shift();
|
||||
pendingExpand.allChildrenCollapsedValue = childrenCollapsedValue;
|
||||
this._pendingActions.push(pendingExpand);
|
||||
this._schedulePendingActionProcessing();
|
||||
}
|
||||
},
|
||||
_showChild: function TreeView__showChild(div, isVisible) {
|
||||
for (var i = 0; i < div.treeChildren.length; i++) {
|
||||
div.treeChildren[i].style.display = isVisible?"":"none";
|
||||
if (!isVisible) {
|
||||
div.treeChildren[i].classList.add("collapsed");
|
||||
this._showChild(div.treeChildren[i], isVisible);
|
||||
}
|
||||
}
|
||||
},
|
||||
_toggle: function TreeView__toggle(div, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
|
||||
var currentCollapsedValue = this._isCollapsed(div);
|
||||
if (newCollapsedValue === undefined)
|
||||
newCollapsedValue = !currentCollapsedValue;
|
||||
if (newCollapsedValue) {
|
||||
div.classList.add("collapsed");
|
||||
this._showChild(div, false);
|
||||
} else {
|
||||
this._resolveChildren(div, true);
|
||||
div.classList.remove("collapsed");
|
||||
this._showChild(div, true);
|
||||
}
|
||||
if (!suppressScrollHeightNotification)
|
||||
this._scrollHeightChanged();
|
||||
},
|
||||
_toggleAll: function TreeView__toggleAll(subtreeRoot, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
|
||||
|
||||
// Reset abort
|
||||
this._abortToggleAll = false;
|
||||
|
||||
// Expands / collapses all child nodes, too.
|
||||
|
||||
if (newCollapsedValue === undefined)
|
||||
newCollapsedValue = !this._isCollapsed(subtreeRoot);
|
||||
if (!newCollapsedValue) {
|
||||
// expanding
|
||||
this._resolveChildren(subtreeRoot, newCollapsedValue);
|
||||
}
|
||||
this._toggle(subtreeRoot, newCollapsedValue, true);
|
||||
for (var i = 0; i < subtreeRoot.treeChildren.length; ++i) {
|
||||
this._toggleAll(subtreeRoot.treeChildren[i], newCollapsedValue, true);
|
||||
}
|
||||
if (!suppressScrollHeightNotification)
|
||||
this._scrollHeightChanged();
|
||||
},
|
||||
_getParent: function TreeView__getParent(div) {
|
||||
return div.treeParent;
|
||||
},
|
||||
_getFirstChild: function TreeView__getFirstChild(div) {
|
||||
if (this._isCollapsed(div))
|
||||
return null;
|
||||
var child = div.treeChildren[0];
|
||||
return child;
|
||||
},
|
||||
_getLastChild: function TreeView__getLastChild(div) {
|
||||
if (this._isCollapsed(div))
|
||||
return div;
|
||||
var lastChild = div.treeChildren[div.treeChildren.length-1];
|
||||
if (lastChild == null)
|
||||
return div;
|
||||
return this._getLastChild(lastChild);
|
||||
},
|
||||
_getPrevSib: function TreeView__getPevSib(div) {
|
||||
if (div.treeParent == null)
|
||||
return null;
|
||||
var nodeIndex = div.treeParent.treeChildren.indexOf(div);
|
||||
if (nodeIndex == 0)
|
||||
return null;
|
||||
return div.treeParent.treeChildren[nodeIndex-1];
|
||||
},
|
||||
_getNextSib: function TreeView__getNextSib(div) {
|
||||
if (div.treeParent == null)
|
||||
return null;
|
||||
var nodeIndex = div.treeParent.treeChildren.indexOf(div);
|
||||
if (nodeIndex == div.treeParent.treeChildren.length - 1)
|
||||
return this._getNextSib(div.treeParent);
|
||||
return div.treeParent.treeChildren[nodeIndex+1];
|
||||
},
|
||||
_scheduleScrollIntoView: function TreeView__scheduleScrollIntoView(element, maxImportantWidth) {
|
||||
// Schedule this on the animation frame otherwise we may run this more then once per frames
|
||||
// causing more work then needed.
|
||||
var self = this;
|
||||
if (self._pendingAnimationFrame != null) {
|
||||
return;
|
||||
}
|
||||
self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() {
|
||||
cancelAnimationFrame(self._pendingAnimationFrame);
|
||||
self._pendingAnimationFrame = null;
|
||||
self._scrollIntoView(element, maxImportantWidth);
|
||||
});
|
||||
},
|
||||
_scrollIntoView: function TreeView__scrollIntoView(element, maxImportantWidth) {
|
||||
// Make sure that element is inside the visible part of our scrollbox by
|
||||
// adjusting the scroll positions. If element is wider or
|
||||
// higher than the scroll port, the left and top edges are prioritized over
|
||||
// the right and bottom edges.
|
||||
// If maxImportantWidth is set, parts of the beyond this widths are
|
||||
// considered as not important; they'll not be moved into view.
|
||||
|
||||
if (maxImportantWidth === undefined)
|
||||
maxImportantWidth = Infinity;
|
||||
|
||||
var visibleRect = {
|
||||
left: this._horizontalScrollbox.getBoundingClientRect().left + 150, // TODO: un-hardcode 150
|
||||
top: this._verticalScrollbox.getBoundingClientRect().top,
|
||||
right: this._horizontalScrollbox.getBoundingClientRect().right,
|
||||
bottom: this._verticalScrollbox.getBoundingClientRect().bottom
|
||||
}
|
||||
var r = element.getBoundingClientRect();
|
||||
var right = Math.min(r.right, r.left + maxImportantWidth);
|
||||
var leftCutoff = visibleRect.left - r.left;
|
||||
var rightCutoff = right - visibleRect.right;
|
||||
var topCutoff = visibleRect.top - r.top;
|
||||
var bottomCutoff = r.bottom - visibleRect.bottom;
|
||||
if (leftCutoff > 0)
|
||||
this._horizontalScrollbox.scrollLeft -= leftCutoff;
|
||||
else if (rightCutoff > 0)
|
||||
this._horizontalScrollbox.scrollLeft += Math.min(rightCutoff, -leftCutoff);
|
||||
if (topCutoff > 0)
|
||||
this._verticalScrollbox.scrollTop -= topCutoff;
|
||||
else if (bottomCutoff > 0)
|
||||
this._verticalScrollbox.scrollTop += Math.min(bottomCutoff, -topCutoff);
|
||||
},
|
||||
_select: function TreeView__select(li) {
|
||||
if (this._selectedNode != null) {
|
||||
this._selectedNode.treeLine.classList.remove("selected");
|
||||
this._selectedNode = null;
|
||||
}
|
||||
if (li) {
|
||||
li.treeLine.classList.add("selected");
|
||||
this._selectedNode = li;
|
||||
var functionName = li.treeLine.querySelector(".functionName");
|
||||
this._scheduleScrollIntoView(functionName, 400);
|
||||
this._fireEvent("select", li.data);
|
||||
}
|
||||
updateDocumentURL();
|
||||
},
|
||||
_isCollapsed: function TreeView__isCollapsed(div) {
|
||||
return div.classList.contains("collapsed");
|
||||
},
|
||||
_getParentTreeViewNode: function TreeView__getParentTreeViewNode(node) {
|
||||
while (node) {
|
||||
if (node.nodeType != node.ELEMENT_NODE)
|
||||
break;
|
||||
if (node.classList.contains("treeViewNode"))
|
||||
return node;
|
||||
node = node.parentNode;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
_onclick: function TreeView__onclick(event) {
|
||||
var target = event.target;
|
||||
var node = this._getParentTreeViewNode(target);
|
||||
if (!node)
|
||||
return;
|
||||
if (target.classList.contains("expandCollapseButton")) {
|
||||
if (event.altKey)
|
||||
this._toggleAll(node);
|
||||
else
|
||||
this._toggle(node);
|
||||
} else if (target.classList.contains("focusCallstackButton")) {
|
||||
this._fireEvent("focusCallstackButtonClicked", node.data);
|
||||
} else {
|
||||
this._select(node);
|
||||
if (event.detail == 2) // dblclick
|
||||
this._toggle(node);
|
||||
}
|
||||
},
|
||||
_onkeypress: function TreeView__onkeypress(event) {
|
||||
if (event.ctrlKey || event.altKey || event.metaKey)
|
||||
return;
|
||||
|
||||
this._abortToggleAll = true;
|
||||
|
||||
var selected = this._selectedNode;
|
||||
if (event.keyCode < 37 || event.keyCode > 40) {
|
||||
if (event.keyCode != 0 ||
|
||||
String.fromCharCode(event.charCode) != '*') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if (!selected)
|
||||
return;
|
||||
if (event.keyCode == 37) { // KEY_LEFT
|
||||
var isCollapsed = this._isCollapsed(selected);
|
||||
if (!isCollapsed) {
|
||||
this._toggle(selected);
|
||||
} else {
|
||||
var parent = this._getParent(selected);
|
||||
if (parent != null) {
|
||||
this._select(parent);
|
||||
}
|
||||
}
|
||||
} else if (event.keyCode == 38) { // KEY_UP
|
||||
var prevSib = this._getPrevSib(selected);
|
||||
var parent = this._getParent(selected);
|
||||
if (prevSib != null) {
|
||||
this._select(this._getLastChild(prevSib));
|
||||
} else if (parent != null) {
|
||||
this._select(parent);
|
||||
}
|
||||
} else if (event.keyCode == 39) { // KEY_RIGHT
|
||||
var isCollapsed = this._isCollapsed(selected);
|
||||
if (isCollapsed) {
|
||||
this._toggle(selected);
|
||||
this._syncProcessPendingActionProcessing();
|
||||
} else {
|
||||
// Do KEY_DOWN
|
||||
var nextSib = this._getNextSib(selected);
|
||||
var child = this._getFirstChild(selected);
|
||||
if (child != null) {
|
||||
this._select(child);
|
||||
} else if (nextSib) {
|
||||
this._select(nextSib);
|
||||
}
|
||||
}
|
||||
} else if (event.keyCode == 40) { // KEY_DOWN
|
||||
var nextSib = this._getNextSib(selected);
|
||||
var child = this._getFirstChild(selected);
|
||||
if (child != null) {
|
||||
this._select(child);
|
||||
} else if (nextSib) {
|
||||
this._select(nextSib);
|
||||
}
|
||||
} else if (String.fromCharCode(event.charCode) == '*') {
|
||||
this._toggleAll(selected);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,137 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const gcli = require('gcli/index');
|
||||
|
||||
loader.lazyGetter(this, "gDevTools",
|
||||
() => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools);
|
||||
|
||||
|
||||
module.exports.items = [
|
||||
{
|
||||
name: "profiler",
|
||||
description: gcli.lookup("profilerDesc"),
|
||||
manual: gcli.lookup("profilerManual")
|
||||
},
|
||||
{
|
||||
name: "profiler open",
|
||||
description: gcli.lookup("profilerOpenDesc"),
|
||||
exec: function (args, context) {
|
||||
return gDevTools.showToolbox(context.environment.target, "jsprofiler")
|
||||
.then(function () null);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "profiler close",
|
||||
description: gcli.lookup("profilerCloseDesc"),
|
||||
exec: function (args, context) {
|
||||
let toolbox = gDevTools.getToolbox(context.environment.target);
|
||||
let panel = (toolbox == null) ? null : toolbox.getPanel(id);
|
||||
if (panel == null)
|
||||
return;
|
||||
|
||||
return gDevTools.closeToolbox(context.environment.target)
|
||||
.then(function () null);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "profiler start",
|
||||
description: gcli.lookup("profilerStartDesc"),
|
||||
returnType: "string",
|
||||
exec: function (args, context) {
|
||||
let target = context.environment.target
|
||||
return gDevTools.showToolbox(target, "jsprofiler").then(toolbox => {
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
|
||||
if (panel.recordingProfile)
|
||||
throw gcli.lookup("profilerAlreadyStarted2");
|
||||
|
||||
panel.toggleRecording();
|
||||
return gcli.lookup("profilerStarted2");
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "profiler stop",
|
||||
description: gcli.lookup("profilerStopDesc"),
|
||||
returnType: "string",
|
||||
exec: function (args, context) {
|
||||
let target = context.environment.target
|
||||
return gDevTools.showToolbox(target, "jsprofiler").then(toolbox => {
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
|
||||
if (!panel.recordingProfile)
|
||||
throw gcli.lookup("profilerNotStarted3");
|
||||
|
||||
panel.toggleRecording();
|
||||
return gcli.lookup("profilerStopped");
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "profiler list",
|
||||
description: gcli.lookup("profilerListDesc"),
|
||||
returnType: "profileList",
|
||||
exec: function (args, context) {
|
||||
let toolbox = gDevTools.getToolbox(context.environment.target);
|
||||
let panel = (toolbox == null) ? null : toolbox.getPanel("jsprofiler");
|
||||
|
||||
if (panel == null) {
|
||||
throw gcli.lookup("profilerNotReady");
|
||||
}
|
||||
|
||||
let profileList = [];
|
||||
for ([ uid, profile ] of panel.profiles) {
|
||||
profileList.push({ name: profile.name, started: profile.isStarted });
|
||||
}
|
||||
return profileList;
|
||||
}
|
||||
},
|
||||
{
|
||||
item: "converter",
|
||||
from: "profileList",
|
||||
to: "view",
|
||||
exec: function(profileList, context) {
|
||||
return {
|
||||
html: "<div>" +
|
||||
" <ol>" +
|
||||
" <li forEach='profile of ${profiles}'>${profile.name}</li>" +
|
||||
" ${profile.name} ${profile.started ? '*' : ''}" +
|
||||
" </li>" +
|
||||
" </ol>" +
|
||||
"</div>",
|
||||
data: { profiles: profileList.profiles },
|
||||
options: { allowEval: true }
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "profiler show",
|
||||
description: gcli.lookup("profilerShowDesc"),
|
||||
params: [
|
||||
{
|
||||
name: "name",
|
||||
type: "string",
|
||||
manual: gcli.lookup("profilerShowManual")
|
||||
}
|
||||
],
|
||||
|
||||
exec: function (args, context) {
|
||||
let toolbox = gDevTools.getToolbox(context.environment.target);
|
||||
let panel = (toolbox == null) ? null : toolbox.getPanel(id);
|
||||
|
||||
if (!panel) {
|
||||
throw gcli.lookup("profilerNotReady");
|
||||
}
|
||||
|
||||
let profile = panel.getProfileByName(args.name);
|
||||
if (!profile) {
|
||||
throw gcli.lookup("profilerNotFound");
|
||||
}
|
||||
|
||||
panel.sidebar.selectedItem = panel.sidebar.getItemByProfile(profile);
|
||||
}
|
||||
}];
|
@ -1,16 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
L10N_BUNDLE: "chrome://browser/locale/devtools/profiler.properties",
|
||||
|
||||
PROFILER_ENABLED: "devtools.profiler.enabled",
|
||||
SHOW_PLATFORM_DATA: "devtools.profiler.ui.show-platform-data",
|
||||
|
||||
PROFILE_IDLE: 0,
|
||||
PROFILE_RUNNING: 1,
|
||||
PROFILE_COMPLETED: 2
|
||||
};
|
@ -1,411 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var isJSM = typeof require !== "function";
|
||||
|
||||
// This code is needed because, for whatever reason, mochitest can't
|
||||
// find any requirejs module so we have to load it old school way. :(
|
||||
|
||||
if (isJSM) {
|
||||
var Cu = this["Components"].utils;
|
||||
let XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}).XPCOMUtils;
|
||||
this["loader"] = { lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils) };
|
||||
this["require"] = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
} else {
|
||||
var { Cu } = require("chrome");
|
||||
}
|
||||
|
||||
const { L10N_BUNDLE } = require("devtools/profiler/consts");
|
||||
|
||||
var EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
loader.lazyGetter(this, "L10N", () => new ViewHelpers.L10N(L10N_BUNDLE));
|
||||
|
||||
loader.lazyGetter(this, "gDevTools",
|
||||
() => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools);
|
||||
|
||||
loader.lazyGetter(this, "DebuggerServer",
|
||||
() => Cu.import("resource:///modules/devtools/dbg-server.jsm", {}).DebuggerServer);
|
||||
|
||||
/**
|
||||
* Data structure that contains information that has
|
||||
* to be shared between separate ProfilerController
|
||||
* instances.
|
||||
*/
|
||||
const sharedData = {
|
||||
data: new WeakMap(),
|
||||
controllers: new WeakMap(),
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes a structure representing an individual profile.
|
||||
*/
|
||||
function makeProfile(name, def={}) {
|
||||
if (def.timeStarted == null)
|
||||
def.timeStarted = null;
|
||||
|
||||
if (def.timeEnded == null)
|
||||
def.timeEnded = null;
|
||||
|
||||
return {
|
||||
name: name,
|
||||
timeStarted: def.timeStarted,
|
||||
timeEnded: def.timeEnded,
|
||||
fromConsole: def.fromConsole || false
|
||||
};
|
||||
}
|
||||
|
||||
// Three functions below all operate with sharedData
|
||||
// structure defined above. They should be self-explanatory.
|
||||
|
||||
function addTarget(target) {
|
||||
sharedData.data.set(target, new Map());
|
||||
}
|
||||
|
||||
function getProfiles(target) {
|
||||
return sharedData.data.get(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Object to control the JavaScript Profiler over the remote
|
||||
* debugging protocol.
|
||||
*
|
||||
* @param Target target
|
||||
* A target object as defined in Target.jsm
|
||||
*/
|
||||
function ProfilerController(target) {
|
||||
if (sharedData.controllers.has(target)) {
|
||||
return sharedData.controllers.get(target);
|
||||
}
|
||||
|
||||
this.target = target;
|
||||
this.client = target.client;
|
||||
this.isConnected = false;
|
||||
this.consoleProfiles = [];
|
||||
this.reservedNames = {};
|
||||
|
||||
addTarget(target);
|
||||
|
||||
// Chrome debugging targets have already obtained a reference
|
||||
// to the profiler actor.
|
||||
if (target.chrome) {
|
||||
this.isConnected = true;
|
||||
this.actor = target.form.profilerActor;
|
||||
}
|
||||
|
||||
sharedData.controllers.set(target, this);
|
||||
EventEmitter.decorate(this);
|
||||
};
|
||||
|
||||
ProfilerController.prototype = {
|
||||
target: null,
|
||||
client: null,
|
||||
isConnected: null,
|
||||
consoleProfiles: null,
|
||||
reservedNames: null,
|
||||
|
||||
/**
|
||||
* Return a map of profile results for the current target.
|
||||
*
|
||||
* @return Map
|
||||
*/
|
||||
get profiles() {
|
||||
return getProfiles(this.target);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the profile is currently recording.
|
||||
*
|
||||
* @param object profile
|
||||
* An object made by calling makeProfile function.
|
||||
* @return boolean
|
||||
*/
|
||||
isProfileRecording: function PC_isProfileRecording(profile) {
|
||||
return profile.timeStarted !== null && profile.timeEnded === null;
|
||||
},
|
||||
|
||||
getProfileName: function PC_getProfileName() {
|
||||
let num = 1;
|
||||
let name = L10N.getFormatStr("profiler.profileName", [num]);
|
||||
|
||||
while (this.reservedNames[name]) {
|
||||
num += 1;
|
||||
name = L10N.getFormatStr("profiler.profileName", [num]);
|
||||
}
|
||||
|
||||
this.reservedNames[name] = true;
|
||||
return name;
|
||||
},
|
||||
|
||||
/**
|
||||
* A listener that fires whenever console.profile or console.profileEnd
|
||||
* is called.
|
||||
*
|
||||
* @param string type
|
||||
* Type of a call. Either 'profile' or 'profileEnd'.
|
||||
* @param object data
|
||||
* Event data.
|
||||
*/
|
||||
onConsoleEvent: function (type, data) {
|
||||
let name = data.extra.name;
|
||||
|
||||
let profileStart = () => {
|
||||
if (name && this.profiles.has(name))
|
||||
return;
|
||||
|
||||
// Add profile structure to shared data.
|
||||
let profile = makeProfile(name || this.getProfileName(), {
|
||||
timeStarted: data.extra.currentTime,
|
||||
fromConsole: true
|
||||
});
|
||||
|
||||
this.profiles.set(profile.name, profile);
|
||||
this.consoleProfiles.push(profile.name);
|
||||
this.emit("profileStart", profile);
|
||||
};
|
||||
|
||||
let profileEnd = () => {
|
||||
if (!name && !this.consoleProfiles.length)
|
||||
return;
|
||||
|
||||
if (!name)
|
||||
name = this.consoleProfiles.pop();
|
||||
else
|
||||
this.consoleProfiles.filter((n) => n !== name);
|
||||
|
||||
if (!this.profiles.has(name))
|
||||
return;
|
||||
|
||||
let profile = this.profiles.get(name);
|
||||
if (!this.isProfileRecording(profile))
|
||||
return;
|
||||
|
||||
let profileData = data.extra.profile;
|
||||
profileData.threads = profileData.threads.map((thread) => {
|
||||
let samples = thread.samples.filter((sample) => {
|
||||
return sample.time >= profile.timeStarted;
|
||||
});
|
||||
|
||||
return { samples: samples };
|
||||
});
|
||||
|
||||
profile.timeEnded = data.extra.currentTime;
|
||||
profile.data = profileData;
|
||||
|
||||
this.emit("profileEnd", profile);
|
||||
};
|
||||
|
||||
if (type === "profile")
|
||||
profileStart();
|
||||
|
||||
if (type === "profileEnd")
|
||||
profileEnd();
|
||||
},
|
||||
|
||||
/**
|
||||
* Connects to the client unless we're already connected.
|
||||
*
|
||||
* @param function cb
|
||||
* Function to be called once we're connected. If
|
||||
* the controller is already connected, this function
|
||||
* will be called immediately (synchronously).
|
||||
*/
|
||||
connect: function (cb=function(){}) {
|
||||
if (this.isConnected) {
|
||||
return void cb();
|
||||
}
|
||||
|
||||
// Check if we already have a grip to the listTabs response object
|
||||
// and, if we do, use it to get to the profilerActor. Otherwise,
|
||||
// call listTabs. The problem is that if we call listTabs twice
|
||||
// webconsole tests fail (see bug 872826).
|
||||
|
||||
let register = () => {
|
||||
let data = { events: ["console-api-profiler"] };
|
||||
|
||||
// Check if Gecko Profiler Addon [1] is installed and, if it is,
|
||||
// don't register our own console event listeners. Gecko Profiler
|
||||
// Addon takes care of console.profile and console.profileEnd methods
|
||||
// and we don't want to break it.
|
||||
//
|
||||
// [1] - https://github.com/bgirard/Gecko-Profiler-Addon/
|
||||
|
||||
AddonManager.getAddonByID("jid0-edalmuivkozlouyij0lpdx548bc@jetpack", (addon) => {
|
||||
if (addon && !addon.userDisabled && !addon.softDisabled)
|
||||
return void cb();
|
||||
|
||||
this.request("registerEventNotifications", data, (resp) => {
|
||||
this.client.addListener("eventNotification", (type, resp) => {
|
||||
let toolbox = gDevTools.getToolbox(this.target);
|
||||
if (toolbox == null)
|
||||
return;
|
||||
|
||||
this.onConsoleEvent(resp.subject.action, resp.data);
|
||||
});
|
||||
});
|
||||
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
if (this.target.root) {
|
||||
this.actor = this.target.root.profilerActor;
|
||||
this.isConnected = true;
|
||||
return void register();
|
||||
}
|
||||
|
||||
this.client.listTabs((resp) => {
|
||||
this.actor = resp.profilerActor;
|
||||
this.isConnected = true;
|
||||
register();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds actor and type information to data and sends the request over
|
||||
* the remote debugging protocol.
|
||||
*
|
||||
* @param string type
|
||||
* Method to call on the other side
|
||||
* @param object data
|
||||
* Data to send with the request
|
||||
* @param function cb
|
||||
* A callback function
|
||||
*/
|
||||
request: function (type, data, cb) {
|
||||
data.to = this.actor;
|
||||
data.type = type;
|
||||
this.client.request(data, cb);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the profiler is active.
|
||||
*
|
||||
* @param function cb
|
||||
* Function to be called with a response from the
|
||||
* client. It will be called with two arguments:
|
||||
* an error object (may be null) and a boolean
|
||||
* value indicating if the profiler is active or not.
|
||||
*/
|
||||
isActive: function (cb) {
|
||||
this.request("isActive", {}, (resp) => {
|
||||
cb(resp.error, resp.isActive, resp.currentTime);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new profile and starts the profiler, if needed.
|
||||
*
|
||||
* @param string name
|
||||
* Name of the profile.
|
||||
* @param function cb
|
||||
* Function to be called once the profiler is started
|
||||
* or we get an error. It will be called with a single
|
||||
* argument: an error object (may be null).
|
||||
*/
|
||||
start: function PC_start(name, cb) {
|
||||
if (this.profiles.has(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let profile = makeProfile(name);
|
||||
this.consoleProfiles.push(name);
|
||||
this.profiles.set(name, profile);
|
||||
|
||||
// If profile is already running, no need to do anything.
|
||||
if (this.isProfileRecording(profile)) {
|
||||
return void cb();
|
||||
}
|
||||
|
||||
this.isActive((err, isActive, currentTime) => {
|
||||
if (isActive) {
|
||||
profile.timeStarted = currentTime;
|
||||
return void cb();
|
||||
}
|
||||
|
||||
let params = {
|
||||
entries: 1000000,
|
||||
interval: 1,
|
||||
features: ["js"],
|
||||
};
|
||||
|
||||
this.request("startProfiler", params, (resp) => {
|
||||
if (resp.error) {
|
||||
return void cb(resp.error);
|
||||
}
|
||||
|
||||
profile.timeStarted = 0;
|
||||
cb();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops the profiler. NOTE, that we don't stop the actual
|
||||
* SPS Profiler here. It will be stopped as soon as all
|
||||
* clients disconnect from the profiler actor.
|
||||
*
|
||||
* @param string name
|
||||
* Name of the profile that needs to be stopped.
|
||||
* @param function cb
|
||||
* Function to be called once the profiler is stopped
|
||||
* or we get an error. It will be called with a single
|
||||
* argument: an error object (may be null).
|
||||
*/
|
||||
stop: function PC_stop(name, cb) {
|
||||
if (!this.profiles.has(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let profile = this.profiles.get(name);
|
||||
if (!this.isProfileRecording(profile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.request("getProfile", {}, (resp) => {
|
||||
if (resp.error) {
|
||||
Cu.reportError("Failed to fetch profile data.");
|
||||
return void cb(resp.error, null);
|
||||
}
|
||||
|
||||
let data = resp.profile;
|
||||
profile.timeEnded = resp.currentTime;
|
||||
|
||||
// Filter out all samples that fall out of current
|
||||
// profile's range.
|
||||
|
||||
data.threads = data.threads.map((thread) => {
|
||||
let samples = thread.samples.filter((sample) => {
|
||||
return sample.time >= profile.timeStarted;
|
||||
});
|
||||
|
||||
return { samples: samples };
|
||||
});
|
||||
|
||||
cb(null, data);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup.
|
||||
*/
|
||||
destroy: function PC_destroy() {
|
||||
this.client = null;
|
||||
this.target = null;
|
||||
this.actor = null;
|
||||
}
|
||||
};
|
||||
|
||||
if (isJSM) {
|
||||
var EXPORTED_SYMBOLS = ["ProfilerController"];
|
||||
} else {
|
||||
module.exports = ProfilerController;
|
||||
}
|
@ -1,16 +1,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',
|
||||
]
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -1,127 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
let { Cu } = require("chrome");
|
||||
let EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
const {
|
||||
PROFILE_IDLE,
|
||||
PROFILE_COMPLETED,
|
||||
PROFILE_RUNNING,
|
||||
L10N_BUNDLE
|
||||
} = require("devtools/profiler/consts");
|
||||
|
||||
loader.lazyGetter(this, "L10N", () => new ViewHelpers.L10N(L10N_BUNDLE));
|
||||
|
||||
let stopProfilingString = L10N.getStr("profiler.stopProfilerString");
|
||||
let startProfilingString = L10N.getStr("profiler.startProfilerString");
|
||||
|
||||
function Sidebar(el) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.document = el.ownerDocument;
|
||||
this.widget = new SideMenuWidget(el, { showArrows: true });
|
||||
this.emptyText = L10N.getStr("profiler.sidebarNotice");
|
||||
|
||||
this.widget.addEventListener("select", (ev) => {
|
||||
if (!ev.detail)
|
||||
return;
|
||||
|
||||
this.emit("select", parseInt(ev.detail.value, 10));
|
||||
});
|
||||
}
|
||||
|
||||
Sidebar.prototype = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Adds a new item for a profile to the sidebar. Markup
|
||||
* example:
|
||||
*
|
||||
* <vbox id="profile-1" class="profiler-sidebar-item">
|
||||
* <h3>Profile 1</h3>
|
||||
* <hbox>
|
||||
* <span flex="1">Completed</span>
|
||||
* <a>Save</a>
|
||||
* </hbox>
|
||||
* </vbox>
|
||||
*
|
||||
*/
|
||||
addProfile: function (profile) {
|
||||
let doc = this.document;
|
||||
let vbox = doc.createElement("vbox");
|
||||
let hbox = doc.createElement("hbox");
|
||||
let h3 = doc.createElement("h3");
|
||||
let span = doc.createElement("span");
|
||||
let save = doc.createElement("a");
|
||||
|
||||
vbox.id = "profile-" + profile.uid;
|
||||
vbox.className = "profiler-sidebar-item";
|
||||
|
||||
h3.textContent = profile.name;
|
||||
span.setAttribute("flex", 1);
|
||||
span.textContent = L10N.getStr("profiler.stateIdle");
|
||||
|
||||
save.textContent = L10N.getStr("profiler.save");
|
||||
save.addEventListener("click", (ev) => {
|
||||
ev.preventDefault();
|
||||
this.emit("save", profile.uid);
|
||||
});
|
||||
|
||||
hbox.appendChild(span);
|
||||
hbox.appendChild(save);
|
||||
|
||||
vbox.appendChild(h3);
|
||||
vbox.appendChild(hbox);
|
||||
|
||||
this.push([vbox, profile.uid], {
|
||||
attachment: {
|
||||
name: profile.name,
|
||||
state: PROFILE_IDLE
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getElementByProfile: function (profile) {
|
||||
return this.document.querySelector("#profile-" + profile.uid);
|
||||
},
|
||||
|
||||
getItemByProfile: function (profile) {
|
||||
return this.getItemByValue(profile.uid.toString());
|
||||
},
|
||||
|
||||
setProfileState: function (profile, state) {
|
||||
let item = this.getItemByProfile(profile);
|
||||
let doc = this.document;
|
||||
let label = item.target.querySelector(".profiler-sidebar-item > hbox > span");
|
||||
let toggleButton = doc.getElementById("profiler-start");
|
||||
|
||||
switch (state) {
|
||||
case PROFILE_IDLE:
|
||||
item.target.setAttribute("state", "idle");
|
||||
label.textContent = L10N.getStr("profiler.stateIdle");
|
||||
break;
|
||||
case PROFILE_RUNNING:
|
||||
item.target.setAttribute("state", "running");
|
||||
label.textContent = L10N.getStr("profiler.stateRunning");
|
||||
toggleButton.setAttribute("tooltiptext",stopProfilingString);
|
||||
break;
|
||||
case PROFILE_COMPLETED:
|
||||
item.target.setAttribute("state", "completed");
|
||||
label.textContent = L10N.getStr("profiler.stateCompleted");
|
||||
toggleButton.setAttribute("tooltiptext",startProfilingString);
|
||||
break;
|
||||
default: // Wrong state, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
item.attachment.state = state;
|
||||
this.emit("stateChanged", item);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Sidebar;
|
@ -1,24 +1,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]
|
||||
|
@ -1,33 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const BASE = "http://example.com/browser/browser/devtools/profiler/test/";
|
||||
const URL = BASE + "mock_profiler_bug_834878_page.html";
|
||||
const SCRIPT = BASE + "mock_profiler_bug_834878_script.js";
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function onSetUp(tab, browser, panel) {
|
||||
let data = { uri: SCRIPT, line: 5, isChrome: false };
|
||||
|
||||
panel.displaySource(data).then(function onOpen() {
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let dbg = gDevTools.getToolbox(target).getPanel("jsdebugger");
|
||||
let view = dbg.panelWin.DebuggerView;
|
||||
|
||||
is(view.Sources.selectedValue, data.uri, "URI is different");
|
||||
is(view.editor.getCursor().line, data.line - 1, "Line is different");
|
||||
|
||||
// Test the case where script is already loaded.
|
||||
view.editor.setCursor({ line: 1, ch: 1 });
|
||||
gDevTools.showToolbox(target, "jsprofiler").then(function () {
|
||||
panel.displaySource(data).then(function onOpenAgain() {
|
||||
is(view.editor.getCursor().line, data.line - 1,
|
||||
"Line is different");
|
||||
tearDown(tab);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab1, gPanel1;
|
||||
let gTab2, gPanel2;
|
||||
|
||||
// Tests that you can run the profiler in multiple tabs at the same
|
||||
// time and that closing the debugger panel in one tab doesn't lock
|
||||
// profilers in other tabs.
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
gTab1 = gTab2 = gPanel1 = gPanel2 = null;
|
||||
});
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
openTwoTabs()
|
||||
.then(startTwoProfiles)
|
||||
.then(stopFirstProfile)
|
||||
.then(stopSecondProfile)
|
||||
.then(closeTabs)
|
||||
.then(openTwoTabs)
|
||||
.then(startTwoProfiles)
|
||||
.then(closeFirstPanel)
|
||||
.then(stopSecondProfile)
|
||||
.then(closeTabs)
|
||||
.then(finish);
|
||||
}
|
||||
|
||||
function openTwoTabs() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
setUp(URL, (tab, browser, panel) => {
|
||||
gTab1 = tab;
|
||||
gPanel1 = panel;
|
||||
|
||||
loadTab(URL, (tab, browser) => {
|
||||
gTab2 = tab;
|
||||
openProfiler(tab, () => {
|
||||
let target = TargetFactory.forTab(tab);
|
||||
gPanel2 = gDevTools.getToolbox(target).getPanel("jsprofiler");
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function startTwoProfiles() {
|
||||
let deferred = promise.defer();
|
||||
gPanel1.controller.start("Profile 1", (err) => {
|
||||
ok(!err, "Profile in tab 1 started without errors");
|
||||
gPanel2.controller.start("Profile 1", (err) => {
|
||||
ok(!err, "Profile in tab 2 started without errors");
|
||||
gPanel1.controller.isActive((err, isActive) => {
|
||||
ok(isActive, "Profiler is active");
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function stopFirstProfile() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
gPanel1.controller.stop("Profile 1", (err, data) => {
|
||||
ok(!err, "Profile in tab 1 stopped without errors");
|
||||
ok(data, "Profile in tab 1 returned some data");
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function stopSecondProfile() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
gPanel2.controller.stop("Profile 1", (err, data) => {
|
||||
ok(!err, "Profile in tab 2 stopped without errors");
|
||||
ok(data, "Profile in tab 2 returned some data");
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function closeTabs() {
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
}
|
||||
|
||||
function closeFirstPanel() {
|
||||
let target = TargetFactory.forTab(gTab1);
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
return toolbox.destroy;
|
||||
}
|
@ -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();
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, (tab, browser, panel) => {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
openConsole(tab, testConsoleProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function testConsoleProfile(hud) {
|
||||
hud.jsterm.clearOutput(true);
|
||||
|
||||
let profilesStarted = 0;
|
||||
|
||||
gPanel.once("parsed", () => {
|
||||
let profile = gPanel.activeProfile;
|
||||
|
||||
is(profile.name, "Profile 1", "Profile name is OK");
|
||||
is(gPanel.sidebar.selectedItem, gPanel.sidebar.getItemByProfile(profile), "Sidebar is OK");
|
||||
is(gPanel.sidebar.selectedItem.attachment.state, PROFILE_COMPLETED);
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
|
||||
hud.jsterm.execute("console.profile()");
|
||||
hud.jsterm.execute("console.profileEnd()");
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
const BASE = "http://example.com/browser/browser/devtools/profiler/test/";
|
||||
const PAGE = BASE + "mock_console_api.html";
|
||||
|
||||
let gTab, gPanel, gToolbox;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, (tab, browser, panel) => {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
openProfiler(tab, (toolbox) => {
|
||||
gToolbox = toolbox;
|
||||
loadUrl(PAGE, tab, () => {
|
||||
gPanel.sidebar.on("stateChanged", (_, item) => {
|
||||
if (item.attachment.state !== PROFILE_COMPLETED)
|
||||
return;
|
||||
|
||||
runTests();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
is(getSidebarItem(1).attachment.state, PROFILE_COMPLETED);
|
||||
|
||||
gPanel.once("parsed", () => {
|
||||
function assertSampleAndFinish() {
|
||||
let [win,doc] = getProfileInternals();
|
||||
let sample = doc.getElementsByClassName("samplePercentage");
|
||||
|
||||
if (sample.length <= 0)
|
||||
return void setTimeout(assertSampleAndFinish, 100);
|
||||
|
||||
ok(sample.length > 0, "We have Cleopatra UI displayed");
|
||||
tearDown(gTab, () => {
|
||||
gTab = null;
|
||||
gPanel = null;
|
||||
gToolbox = null;
|
||||
});
|
||||
}
|
||||
|
||||
assertSampleAndFinish();
|
||||
});
|
||||
|
||||
let profile = gPanel.profiles.get(1);
|
||||
gPanel.sidebar.selectedItem = gPanel.sidebar.getItemByProfile(profile);
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, (tab, browser, panel) => {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
openProfiler(tab, runTests);
|
||||
});
|
||||
}
|
||||
|
||||
function runTests(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
let record = gPanel.controls.record;
|
||||
|
||||
panel.once("started", () => {
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_RUNNING);
|
||||
|
||||
openConsole(gTab, (hud) => {
|
||||
panel.once("stopped", () => {
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_COMPLETED);
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
|
||||
hud.jsterm.execute("console.profileEnd()");
|
||||
});
|
||||
});
|
||||
|
||||
record.click();
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, (tab, browser, panel) => {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
openConsole(tab, testConsoleProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function testConsoleProfile(hud) {
|
||||
hud.jsterm.clearOutput(true);
|
||||
|
||||
// Here we start two named profiles and then end one of them.
|
||||
|
||||
let profilesStarted = 0;
|
||||
|
||||
function endProfile() {
|
||||
if (++profilesStarted < 2)
|
||||
return;
|
||||
|
||||
gPanel.controller.off("profileStart", endProfile);
|
||||
gPanel.controller.once("profileEnd", () => openProfiler(gTab, checkProfiles));
|
||||
hud.jsterm.execute("console.profileEnd('Second')");
|
||||
}
|
||||
|
||||
gPanel.controller.on("profileStart", endProfile);
|
||||
hud.jsterm.execute("console.profile('Second')");
|
||||
hud.jsterm.execute("console.profile('Third')");
|
||||
}
|
||||
|
||||
function checkProfiles(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
|
||||
is(getSidebarItem(1, panel).attachment.name, "Second", "Name in sidebar is OK");
|
||||
is(getSidebarItem(1, panel).attachment.state, PROFILE_COMPLETED, "State in sidebar is OK");
|
||||
|
||||
// Make sure we can still stop profiles via the queue pop.
|
||||
|
||||
gPanel.controller.once("profileEnd", () => {
|
||||
openProfiler(gTab, () => {
|
||||
is(getSidebarItem(2, panel).attachment.state, PROFILE_COMPLETED, "State in sidebar is OK");
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
});
|
||||
|
||||
openConsole(gTab, (hud) => hud.jsterm.execute("console.profileEnd()"));
|
||||
}
|
@ -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));
|
||||
});
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function (tab, browser, panel) {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
let record = gPanel.controls.record;
|
||||
|
||||
gPanel.once("started", () => {
|
||||
gPanel.once("stopped", () => {
|
||||
let [ win, doc ] = getProfileInternals(gPanel.activeProfile.uid);
|
||||
|
||||
let expl = "<script>function f() {}</script></textarea><img/src='about:logo'>";
|
||||
let expl2 = "<script>function f() {}</script></pre><img/src='about:logo'>";
|
||||
|
||||
is(win.escapeHTML(expl),
|
||||
"<script>function f() {}</script></textarea><img/src='about:logo'>");
|
||||
|
||||
is(win.escapeHTML(expl2),
|
||||
"<script>function f() {}</script></pre><img/src='about:logo'>");
|
||||
|
||||
tearDown(gTab, () => {
|
||||
gTab = null;
|
||||
gPanel = null;
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
record.click();
|
||||
}, 50);
|
||||
});
|
||||
|
||||
record.click();
|
||||
});
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function onSetUp(tab, browser, panel) {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
function done() {
|
||||
tearDown(gTab, () => { gPanel = null; gTab = null; });
|
||||
}
|
||||
|
||||
Services.prefs.setBoolPref(SHOW_PLATFORM_DATA, false);
|
||||
recordProfile()
|
||||
.then(toggleGeckoDataOption)
|
||||
.then(recordProfile)
|
||||
.then(done);
|
||||
});
|
||||
}
|
||||
|
||||
function recordProfile() {
|
||||
let deferred = promise.defer();
|
||||
let record = gPanel.controls.record;
|
||||
|
||||
gPanel.once("started", () => {
|
||||
gPanel.once("parsed", () => {
|
||||
// We cannot be sure which data is returned by
|
||||
// the profiler within a test. Until we get rid
|
||||
// of Cleopatra, at least.
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
record.click();
|
||||
});
|
||||
|
||||
record.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function toggleGeckoDataOption() {
|
||||
ok(!gPanel.showPlatformData, "showPlatformData is not set");
|
||||
|
||||
Services.prefs.setBoolPref(SHOW_PLATFORM_DATA, true);
|
||||
|
||||
ok(gPanel.showPlatformData, "showPlatformData is set");
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>browser_profiler_io</p>";
|
||||
|
||||
let temp = {};
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm", temp);
|
||||
let FileUtils = temp.FileUtils;
|
||||
let gTab, gPanel;
|
||||
|
||||
let gData = {
|
||||
"libs": "[]", // This property is not important for this test.
|
||||
"meta": {
|
||||
"version": 2,
|
||||
"interval": 1,
|
||||
"stackwalk": 0,
|
||||
"jank": 0,
|
||||
"processType": 0,
|
||||
"platform": "Macintosh",
|
||||
"oscpu": "Intel Mac OS X 10.8",
|
||||
"misc": "rv:25.0",
|
||||
"abi": "x86_64-gcc3",
|
||||
"toolkit": "cocoa",
|
||||
"product": "Firefox"
|
||||
},
|
||||
"threads": [
|
||||
{
|
||||
"samples": [
|
||||
{
|
||||
"name": "(root)",
|
||||
"frames": [
|
||||
{
|
||||
"location": "Startup::XRE_Main",
|
||||
"line": 3871
|
||||
},
|
||||
{
|
||||
"location": "Events::ProcessGeckoEvents",
|
||||
"line": 355
|
||||
},
|
||||
{
|
||||
"location": "Events::ProcessGeckoEvents",
|
||||
"line": 355
|
||||
}
|
||||
],
|
||||
"responsiveness": -0.002963,
|
||||
"time": 8.120823
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function onSetUp(tab, browser, panel) {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
|
||||
|
||||
gPanel.saveProfile(file, gData)
|
||||
.then(gPanel.loadProfile.bind(gPanel, file))
|
||||
.then(checkData);
|
||||
});
|
||||
}
|
||||
|
||||
function checkData() {
|
||||
let profile = gPanel.activeProfile;
|
||||
let item = gPanel.sidebar.getItemByProfile(profile);
|
||||
let data = profile.data;
|
||||
|
||||
is(item.attachment.state, PROFILE_COMPLETED, "Profile is COMPLETED");
|
||||
is(gData.meta.oscpu, data.meta.oscpu, "Meta data is correct");
|
||||
is(gData.threads[0].samples.length, 1, "There's one sample");
|
||||
is(gData.threads[0].samples[0].name, "(root)", "Sample is correct");
|
||||
|
||||
tearDown(gTab, () => { gPanel = null; gTab = null; });
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let temp = {};
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", temp);
|
||||
let DebuggerServer = temp.DebuggerServer;
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm", temp);
|
||||
let DebuggerClient = temp.DebuggerClient;
|
||||
let debuggerSocketConnect = temp.debuggerSocketConnect;
|
||||
|
||||
Cu.import("resource:///modules/devtools/profiler/controller.js", temp);
|
||||
let ProfilerController = temp.ProfilerController;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
Services.prefs.setBoolPref(REMOTE_ENABLED, true);
|
||||
|
||||
loadTab(URL, function onTabLoad(tab, browser) {
|
||||
DebuggerServer.init(function () true);
|
||||
DebuggerServer.addBrowserActors();
|
||||
is(DebuggerServer.listeningSockets, 0);
|
||||
|
||||
DebuggerServer.openListener(2929);
|
||||
is(DebuggerServer.listeningSockets, 1);
|
||||
|
||||
let transport = debuggerSocketConnect("127.0.0.1", 2929);
|
||||
let client = new DebuggerClient(transport);
|
||||
client.connect(function onClientConnect() {
|
||||
let target = { isRemote: true, client: client };
|
||||
let controller = new ProfilerController(target);
|
||||
|
||||
controller.connect(function onControllerConnect() {
|
||||
// If this callback is called, this means listTabs call worked.
|
||||
// Which means that the transport worked. Time to finish up this
|
||||
// test.
|
||||
|
||||
function onShutdown() {
|
||||
window.removeEventListener("Debugger:Shutdown", onShutdown, true);
|
||||
transport = client = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
window.addEventListener("Debugger:Shutdown", onShutdown, true);
|
||||
|
||||
client.close(function () {
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function onSetUp(tab, browser, panel) {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
function done() {
|
||||
tearDown(gTab, () => { gPanel = null; gTab = null; });
|
||||
}
|
||||
|
||||
startRecording()
|
||||
.then(stopRecording)
|
||||
.then(startRecordingAgain)
|
||||
.then(stopRecording)
|
||||
.then(switchBackToTheFirstOne)
|
||||
.then(done);
|
||||
});
|
||||
}
|
||||
|
||||
function startRecording() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
ok(gPanel, "Profiler panel exists");
|
||||
ok(!gPanel.activeProfile, "Active profile doesn't exist");
|
||||
ok(!gPanel.recordingProfile, "Recording profile doesn't exist");
|
||||
|
||||
let record = gPanel.controls.record;
|
||||
ok(record, "Record button exists.");
|
||||
ok(!record.getAttribute("checked"), "Record button is unchecked");
|
||||
|
||||
gPanel.once("started", () => {
|
||||
let item = gPanel.sidebar.getItemByProfile(gPanel.recordingProfile);
|
||||
is(item.attachment.name, "Profile 1");
|
||||
is(item.attachment.state, PROFILE_RUNNING);
|
||||
is(record.getAttribute("tooltiptext"), "Stop profiling");
|
||||
|
||||
gPanel.controller.isActive(function (err, isActive) {
|
||||
ok(isActive, "Profiler is running");
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
record.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function stopRecording() {
|
||||
let deferred = promise.defer();
|
||||
let record = gPanel.controls.record;
|
||||
|
||||
gPanel.once("parsed", () => {
|
||||
let item = gPanel.sidebar.getItemByProfile(gPanel.activeProfile);
|
||||
is(item.attachment.state, PROFILE_COMPLETED);
|
||||
is(record.getAttribute("tooltiptext"), "Start profiling");
|
||||
|
||||
function assertSample() {
|
||||
let [ win, doc ] = getProfileInternals();
|
||||
let sample = doc.getElementsByClassName("samplePercentage");
|
||||
|
||||
if (sample.length <= 0) {
|
||||
return void setTimeout(assertSample, 100);
|
||||
}
|
||||
|
||||
ok(sample.length > 0, "We have some items displayed");
|
||||
is(sample[0].innerHTML, "100.0%", "First percentage is 100%");
|
||||
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
assertSample();
|
||||
});
|
||||
|
||||
setTimeout(function () gPanel.controls.record.click(), 100);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function startRecordingAgain() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let record = gPanel.controls.record;
|
||||
ok(!record.getAttribute("checked"), "Record button is unchecked");
|
||||
|
||||
gPanel.once("started", () => {
|
||||
ok(gPanel.activeProfile !== gPanel.recordingProfile);
|
||||
|
||||
let item = gPanel.sidebar.getItemByProfile(gPanel.recordingProfile);
|
||||
is(item.attachment.name, "Profile 2");
|
||||
is(item.attachment.state, PROFILE_RUNNING);
|
||||
is(record.getAttribute("tooltiptext"), "Stop profiling");
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
record.click();
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function switchBackToTheFirstOne() {
|
||||
let deferred = promise.defer();
|
||||
let button = gPanel.sidebar.getElementByProfile({ uid: 1 });
|
||||
let item = gPanel.sidebar.getItemByProfile({ uid: 1 });
|
||||
|
||||
gPanel.once("profileSwitched", () => {
|
||||
is(gPanel.activeProfile.uid, 1, "activeProfile is correct");
|
||||
is(gPanel.sidebar.selectedItem, item, "selectedItem is correct");
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
button.click();
|
||||
return deferred.promise;
|
||||
}
|
@ -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";
|
||||
|
@ -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>
|
@ -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>
|
@ -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;
|
||||
}
|
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
@ -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");
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user