mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 795268 - Integrate SPS Profiler; r=rcampbell
This commit is contained in:
parent
0f660cd9d4
commit
c0de41f3cb
@ -1047,6 +1047,9 @@ pref("devtools.debugger.ui.variables-sorting-enabled", true);
|
||||
pref("devtools.debugger.ui.variables-only-enum-visible", false);
|
||||
pref("devtools.debugger.ui.variables-searchbox-visible", false);
|
||||
|
||||
// Enable the Profiler
|
||||
pref("devtools.profiler.enabled", true);
|
||||
|
||||
// Enable the Tilt inspector
|
||||
pref("devtools.tilt.enabled", true);
|
||||
pref("devtools.tilt.intro_transition", true);
|
||||
|
@ -27,6 +27,7 @@ DIRS = \
|
||||
shared \
|
||||
responsivedesign \
|
||||
framework \
|
||||
profiler \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
@ -18,6 +18,7 @@ const inspectorProps = "chrome://browser/locale/devtools/inspector.properties";
|
||||
const debuggerProps = "chrome://browser/locale/devtools/debugger.properties";
|
||||
const styleEditorProps = "chrome://browser/locale/devtools/styleeditor.properties";
|
||||
const webConsoleProps = "chrome://browser/locale/devtools/webconsole.properties";
|
||||
const profilerProps = "chrome://browser/locale/devtools/profiler.properties";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
@ -39,6 +40,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "StyleEditorPanel",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "InspectorPanel",
|
||||
"resource:///modules/devtools/InspectorPanel.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ProfilerPanel",
|
||||
"resource:///modules/devtools/ProfilerPanel.jsm");
|
||||
|
||||
// Strings
|
||||
XPCOMUtils.defineLazyGetter(this, "webConsoleStrings",
|
||||
function() Services.strings.createBundle(webConsoleProps));
|
||||
@ -52,6 +56,9 @@ XPCOMUtils.defineLazyGetter(this, "styleEditorStrings",
|
||||
XPCOMUtils.defineLazyGetter(this, "inspectorStrings",
|
||||
function() Services.strings.createBundle(inspectorProps));
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "profilerStrings",
|
||||
function() Services.strings.createBundle(profilerProps));
|
||||
|
||||
// Definitions
|
||||
let webConsoleDefinition = {
|
||||
id: "webconsole",
|
||||
@ -131,6 +138,28 @@ let styleEditorDefinition = {
|
||||
}
|
||||
};
|
||||
|
||||
let profilerDefinition = {
|
||||
id: "jsprofiler",
|
||||
killswitch: "devtools.profiler.enabled",
|
||||
icon: "chrome://browser/skin/devtools/tools-icons-small.png",
|
||||
url: "chrome://browser/content/profiler.xul",
|
||||
label: l10n("profiler.label", profilerStrings),
|
||||
|
||||
isTargetSupported: function (target) {
|
||||
if (target.isRemote || target.isChrome) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
build: function (frame, target) {
|
||||
let panel = new ProfilerPanel(frame, target);
|
||||
return panel.open();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.defaultTools = [
|
||||
styleEditorDefinition,
|
||||
webConsoleDefinition,
|
||||
@ -138,6 +167,10 @@ this.defaultTools = [
|
||||
inspectorDefinition,
|
||||
];
|
||||
|
||||
if (Services.prefs.getBoolPref("devtools.profiler.enabled")) {
|
||||
defaultTools.push(profilerDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup l10n string from a string bundle.
|
||||
*
|
||||
|
@ -27,6 +27,24 @@ browser.jar:
|
||||
content/browser/debugger-view.js (debugger/debugger-view.js)
|
||||
content/browser/debugger-toolbar.js (debugger/debugger-toolbar.js)
|
||||
content/browser/debugger-panes.js (debugger/debugger-panes.js)
|
||||
* content/browser/profiler.xul (profiler/profiler.xul)
|
||||
content/browser/profiler.css (profiler/profiler.css)
|
||||
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/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/filter.png (profiler/cleopatra/images/filter.png)
|
||||
content/browser/devtools/profiler/cleopatra/images/noise.png (profiler/cleopatra/images/noise.png)
|
||||
content/browser/devtools/profiler/cleopatra/images/showall.png (profiler/cleopatra/images/showall.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/commandline.css (commandline/commandline.css)
|
||||
content/browser/devtools/commandlineoutput.xhtml (commandline/commandlineoutput.xhtml)
|
||||
content/browser/devtools/commandlinetooltip.xhtml (commandline/commandlinetooltip.xhtml)
|
||||
|
17
browser/devtools/profiler/Makefile.in
Normal file
17
browser/devtools/profiler/Makefile.in
Normal file
@ -0,0 +1,17 @@
|
||||
# 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/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
TEST_DIRS += test
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
libs::
|
||||
$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
|
208
browser/devtools/profiler/ProfilerController.jsm
Normal file
208
browser/devtools/profiler/ProfilerController.jsm
Normal file
@ -0,0 +1,208 @@
|
||||
/* 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 Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||
|
||||
let EXPORTED_SYMBOLS = ["ProfilerController"];
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () {
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
return DebuggerServer;
|
||||
});
|
||||
|
||||
/**
|
||||
* Object acting as a mediator between the ProfilerController and
|
||||
* DebuggerServer.
|
||||
*/
|
||||
function ProfilerConnection() {
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
let transport = DebuggerServer.connectPipe();
|
||||
this.client = new DebuggerClient(transport);
|
||||
}
|
||||
|
||||
ProfilerConnection.prototype = {
|
||||
actor: null,
|
||||
|
||||
/**
|
||||
* Connects to a debugee and executes a callback when ready.
|
||||
*
|
||||
* @param function aCallback
|
||||
* Function to be called once we're connected to the client.
|
||||
*/
|
||||
connect: function PCn_connect(aCallback) {
|
||||
let client = this.client;
|
||||
|
||||
client.connect(function (aType, aTraits) {
|
||||
client.listTabs(function (aResponse) {
|
||||
this.actor = aResponse.profilerActor;
|
||||
aCallback();
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a message to check if the profiler is currently active.
|
||||
*
|
||||
* @param function aCallback
|
||||
* Function to be called once we have a response from
|
||||
* the client. It will be called with a single argument
|
||||
* containing a response object.
|
||||
*/
|
||||
isActive: function PCn_isActive(aCallback) {
|
||||
var message = { to: this.actor, type: "isActive" };
|
||||
this.client.request(message, aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a message to start a profiler.
|
||||
*
|
||||
* @param function aCallback
|
||||
* Function to be called once the profiler is running.
|
||||
* It will be called with a single argument containing
|
||||
* a response object.
|
||||
*/
|
||||
startProfiler: function PCn_startProfiler(aCallback) {
|
||||
var message = {
|
||||
to: this.actor,
|
||||
type: "startProfiler",
|
||||
entries: 1000000,
|
||||
interval: 1,
|
||||
features: ["js"],
|
||||
};
|
||||
this.client.request(message, aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a message to stop a profiler.
|
||||
*
|
||||
* @param function aCallback
|
||||
* Function to be called once the profiler is idle.
|
||||
* It will be called with a single argument containing
|
||||
* a response object.
|
||||
*/
|
||||
stopProfiler: function PCn_stopProfiler(aCallback) {
|
||||
var message = { to: this.actor, type: "stopProfiler" };
|
||||
this.client.request(message, aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a message to get the generated profile data.
|
||||
*
|
||||
* @param function aCallback
|
||||
* Function to be called once we have the data.
|
||||
* It will be called with a single argument containing
|
||||
* a response object.
|
||||
*/
|
||||
getProfileData: function PCn_getProfileData(aCallback) {
|
||||
var message = { to: this.actor, type: "getProfile" };
|
||||
this.client.request(message, aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup.
|
||||
*/
|
||||
destroy: function PCn_destroy() {
|
||||
this.client.close(function () {
|
||||
this.client = null;
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Object defining the profiler controller components.
|
||||
*/
|
||||
function ProfilerController() {
|
||||
this.profiler = new ProfilerConnection();
|
||||
this._connected = false;
|
||||
}
|
||||
|
||||
ProfilerController.prototype = {
|
||||
/**
|
||||
* Connects to the client unless we're already connected.
|
||||
*
|
||||
* @param function aCallback
|
||||
* Function to be called once we're connected. If
|
||||
* the controller is already connected, this function
|
||||
* will be called immediately (synchronously).
|
||||
*/
|
||||
connect: function (aCallback) {
|
||||
if (this._connected) {
|
||||
aCallback();
|
||||
}
|
||||
|
||||
this.profiler.connect(function onConnect() {
|
||||
this._connected = true;
|
||||
aCallback();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the profiler is active.
|
||||
*
|
||||
* @param function aCallback
|
||||
* 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 PC_isActive(aCallback) {
|
||||
this.profiler.isActive(function onActive(aResponse) {
|
||||
aCallback(aResponse.error, aResponse.isActive);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts the profiler.
|
||||
*
|
||||
* @param function aCallback
|
||||
* 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(aCallback) {
|
||||
this.profiler.startProfiler(function onStart(aResponse) {
|
||||
aCallback(aResponse.error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops the profiler.
|
||||
*
|
||||
* @param function aCallback
|
||||
* 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(aCallback) {
|
||||
this.profiler.getProfileData(function onData(aResponse) {
|
||||
let data = aResponse.profile;
|
||||
if (aResponse.error) {
|
||||
Cu.reportError("Failed to fetch profile data before stopping the profiler.");
|
||||
}
|
||||
|
||||
this.profiler.stopProfiler(function onStop(aResponse) {
|
||||
aCallback(aResponse.error, data);
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup.
|
||||
*/
|
||||
destroy: function PC_destroy(aCallback) {
|
||||
this.profiler.destroy();
|
||||
this.profiler = null;
|
||||
}
|
||||
};
|
393
browser/devtools/profiler/ProfilerPanel.jsm
Normal file
393
browser/devtools/profiler/ProfilerPanel.jsm
Normal file
@ -0,0 +1,393 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource:///modules/devtools/ProfilerController.jsm");
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/devtools/EventEmitter.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ProfilerPanel"];
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () {
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
return DebuggerServer;
|
||||
});
|
||||
|
||||
/**
|
||||
* An instance of a profile UI. Profile UI consists of
|
||||
* an iframe with Cleopatra loaded in it and some
|
||||
* surrounding meta-data (such as uids).
|
||||
*
|
||||
* Its main function is to talk to the Cleopatra instance
|
||||
* inside of the iframe.
|
||||
*
|
||||
* ProfileUI is also an event emitter. Currently, it emits
|
||||
* only one event, 'ready', when Cleopatra is done loading.
|
||||
* You can also check '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 ProfileUI(uid, panel) {
|
||||
let doc = panel.document;
|
||||
let win = panel.window;
|
||||
|
||||
new EventEmitter(this);
|
||||
|
||||
this.isReady = false;
|
||||
this.panel = panel;
|
||||
this.uid = uid;
|
||||
|
||||
this.iframe = doc.createElement("iframe");
|
||||
this.iframe.setAttribute("flex", "1");
|
||||
this.iframe.setAttribute("id", "profiler-cleo-" + uid);
|
||||
this.iframe.setAttribute("src", "devtools/cleopatra.html?" + uid);
|
||||
this.iframe.setAttribute("hidden", "true");
|
||||
|
||||
// Append our iframe and subscribe to postMessage events.
|
||||
// They'll tell us when the underlying page is done loading
|
||||
// or when user clicks on start/stop buttons.
|
||||
|
||||
doc.getElementById("profiler-report").appendChild(this.iframe);
|
||||
win.addEventListener("message", function (event) {
|
||||
if (parseInt(event.data.uid, 10) !== parseInt(this.uid, 10)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.data.status) {
|
||||
case "loaded":
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
break;
|
||||
case "start":
|
||||
// Start profiling and, once started, notify the
|
||||
// underlying page so that it could update the UI.
|
||||
this.panel.startProfiling(function onStart() {
|
||||
var data = JSON.stringify({task: "onStarted"});
|
||||
this.iframe.contentWindow.postMessage(data, "*");
|
||||
}.bind(this));
|
||||
|
||||
break;
|
||||
case "stop":
|
||||
// Stop profiling and, once stopped, notify the
|
||||
// underlying page so that it could update the UI.
|
||||
this.panel.stopProfiling(function onStop() {
|
||||
var data = JSON.stringify({task: "onStopped"});
|
||||
this.iframe.contentWindow.postMessage(data, "*");
|
||||
}.bind(this));
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
ProfileUI.prototype = {
|
||||
show: function PUI_show() {
|
||||
this.iframe.removeAttribute("hidden");
|
||||
},
|
||||
|
||||
hide: function PUI_hide() {
|
||||
this.iframe.setAttribute("hidden", true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send raw profiling data to Cleopatra for parsing.
|
||||
*
|
||||
* @param object data
|
||||
* Raw profiling data from the SPS Profiler.
|
||||
* @param function onParsed
|
||||
* A callback to be called when Cleopatra finishes
|
||||
* parsing and displaying results.
|
||||
*
|
||||
*/
|
||||
parse: function PUI_parse(data, onParsed) {
|
||||
if (!this.isReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
let win = this.iframe.contentWindow;
|
||||
|
||||
win.postMessage(JSON.stringify({
|
||||
task: "receiveProfileData",
|
||||
rawProfile: data
|
||||
}), "*");
|
||||
|
||||
let poll = function pollBreadcrumbs() {
|
||||
let wait = this.panel.window.setTimeout.bind(null, poll, 100);
|
||||
let trail = win.gBreadcrumbTrail;
|
||||
|
||||
if (!trail) {
|
||||
return wait();
|
||||
}
|
||||
|
||||
if (!trail._breadcrumbs || !trail._breadcrumbs.length) {
|
||||
return wait();
|
||||
}
|
||||
|
||||
onParsed();
|
||||
}.bind(this);
|
||||
|
||||
poll();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys the ProfileUI instance.
|
||||
*/
|
||||
destroy: function PUI_destroy() {
|
||||
this.isReady = null
|
||||
this.panel = null;
|
||||
this.uid = null;
|
||||
this.iframe = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Profiler panel. It is responsible for creating and managing
|
||||
* different profile instances (see ProfileUI).
|
||||
*
|
||||
* ProfilerPanel is an event emitter. It can emit the following
|
||||
* events:
|
||||
*
|
||||
* - ready: after the panel is done loading everything,
|
||||
* including the default profile instance.
|
||||
* - started: after the panel successfuly starts our SPS
|
||||
* profiler.
|
||||
* - stopped: after the panel successfuly stops our SPS
|
||||
* profiler and is ready to hand over profiling
|
||||
* data
|
||||
* - parsed: after Cleopatra finishes parsing profiling
|
||||
* data.
|
||||
* - destroyed: after the panel cleans up after itself and
|
||||
* is ready to be destroyed.
|
||||
*
|
||||
* The following events are used mainly by tests to prevent
|
||||
* accidential oranges:
|
||||
*
|
||||
* - profileCreated: after a new profile is created.
|
||||
* - profileSwitched: after user switches to a different
|
||||
* profile.
|
||||
*/
|
||||
function ProfilerPanel(frame, toolbox) {
|
||||
this.isReady = false;
|
||||
this.window = frame.window;
|
||||
this.document = frame.document;
|
||||
this.target = toolbox.target;
|
||||
this.controller = new ProfilerController();
|
||||
|
||||
this.profiles = new Map();
|
||||
this._uid = 0;
|
||||
|
||||
new EventEmitter(this);
|
||||
}
|
||||
|
||||
ProfilerPanel.prototype = {
|
||||
isReady: null,
|
||||
window: null,
|
||||
document: null,
|
||||
target: null,
|
||||
controller: null,
|
||||
profiles: null,
|
||||
|
||||
_uid: null,
|
||||
_activeUid: null,
|
||||
|
||||
get activeProfile() {
|
||||
return this.profiles.get(this._activeUid);
|
||||
},
|
||||
|
||||
set activeProfile(profile) {
|
||||
this._activeUid = profile.uid;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open a debug connection and, on success, switch to
|
||||
* the newly created profile.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
open: function PP_open() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
this.controller.connect(function onConnect() {
|
||||
let create = this.document.getElementById("profiler-create");
|
||||
create.addEventListener("click", this.createProfile.bind(this), false);
|
||||
create.removeAttribute("disabled");
|
||||
|
||||
let profile = this.createProfile();
|
||||
this.switchToProfile(profile, function () {
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
|
||||
deferred.resolve(this);
|
||||
}.bind(this))
|
||||
}.bind(this));
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new profile instance (see ProfileUI) and
|
||||
* adds an appropriate item to the sidebar. Note that
|
||||
* this method doesn't automatically switch user to
|
||||
* the newly created profile, they have do to switch
|
||||
* explicitly.
|
||||
*
|
||||
* @return ProfilerPanel
|
||||
*/
|
||||
createProfile: function PP_addProfile() {
|
||||
let uid = ++this._uid;
|
||||
let list = this.document.getElementById("profiles-list");
|
||||
let item = this.document.createElement("li");
|
||||
let wrap = this.document.createElement("h1");
|
||||
|
||||
item.setAttribute("id", "profile-" + uid);
|
||||
item.setAttribute("data-uid", uid);
|
||||
item.addEventListener("click", function (ev) {
|
||||
let uid = parseInt(ev.target.getAttribute("data-uid"), 10);
|
||||
this.switchToProfile(this.profiles.get(uid));
|
||||
}.bind(this), false);
|
||||
|
||||
wrap.className = "profile-name";
|
||||
wrap.textContent = "Profile " + uid;
|
||||
|
||||
item.appendChild(wrap);
|
||||
list.appendChild(item);
|
||||
|
||||
let profile = new ProfileUI(uid, this);
|
||||
this.profiles.set(uid, profile);
|
||||
|
||||
this.emit("profileCreated", uid);
|
||||
return profile;
|
||||
},
|
||||
|
||||
/**
|
||||
* Switches to a different profile by making its instance an
|
||||
* active one.
|
||||
*
|
||||
* @param ProfileUI profile
|
||||
* A profile instance to switch to.
|
||||
* @param function onLoad
|
||||
* A function to call when profile instance is ready.
|
||||
* If the instance is already loaded, onLoad will be
|
||||
* called synchronously.
|
||||
*/
|
||||
switchToProfile: function PP_switchToProfile(profile, onLoad) {
|
||||
let doc = this.document;
|
||||
|
||||
if (this.activeProfile) {
|
||||
this.activeProfile.hide();
|
||||
}
|
||||
|
||||
let active = doc.querySelector("#profiles-list > li.splitview-active");
|
||||
if (active) {
|
||||
active.className = "";
|
||||
}
|
||||
|
||||
doc.getElementById("profile-" + profile.uid).className = "splitview-active";
|
||||
profile.show();
|
||||
this.activeProfile = profile;
|
||||
|
||||
if (profile.isReady) {
|
||||
this.emit("profileSwitched", profile.uid);
|
||||
onLoad();
|
||||
return;
|
||||
}
|
||||
|
||||
profile.once("ready", function () {
|
||||
this.emit("profileSwitched", profile.uid);
|
||||
onLoad();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Start collecting profile data.
|
||||
*
|
||||
* @param function onStart
|
||||
* A function to call once we get the message
|
||||
* that profiling had been successfuly started.
|
||||
*/
|
||||
startProfiling: function PP_startProfiling(onStart) {
|
||||
this.controller.start(function (err) {
|
||||
if (err) {
|
||||
Cu.reportError("ProfilerController.start: " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
onStart();
|
||||
this.emit("started");
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop collecting profile data and send it to Cleopatra
|
||||
* for parsing.
|
||||
*
|
||||
* @param function onStop
|
||||
* A function to call once we get the message
|
||||
* that profiling had been successfuly stopped.
|
||||
*/
|
||||
stopProfiling: function PP_stopProfiling(onStop) {
|
||||
this.controller.isActive(function (err, isActive) {
|
||||
if (err) {
|
||||
Cu.reportError("ProfilerController.isActive: " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.controller.stop(function (err, data) {
|
||||
if (err) {
|
||||
Cu.reportError("ProfilerController.stop: " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeProfile.parse(data, function onParsed() {
|
||||
this.emit("parsed");
|
||||
}.bind(this));
|
||||
|
||||
onStop();
|
||||
this.emit("stopped");
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup.
|
||||
*/
|
||||
destroy: function PP_destroy() {
|
||||
if (this.profiles) {
|
||||
let uid = this._uid;
|
||||
|
||||
while (uid >= 0) {
|
||||
if (this.profiles.has(uid)) {
|
||||
this.profiles.get(uid).destroy();
|
||||
this.profiles.delete(uid);
|
||||
}
|
||||
uid -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.controller) {
|
||||
this.controller.destroy();
|
||||
}
|
||||
|
||||
this.isReady = null;
|
||||
this.window = null;
|
||||
this.document = null;
|
||||
this.target = null;
|
||||
this.controller = null;
|
||||
this.profiles = null;
|
||||
this._uid = null;
|
||||
this._activeUid = null;
|
||||
|
||||
this.emit("destroyed");
|
||||
}
|
||||
};
|
29
browser/devtools/profiler/cleopatra/cleopatra.html
Executable file
29
browser/devtools/profiler/cleopatra/cleopatra.html
Executable file
@ -0,0 +1,29 @@
|
||||
<!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/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>
|
||||
|
||||
<link rel="shortcut icon" href="favicon.png" />
|
||||
</head>
|
||||
|
||||
<body onload="notifyParent('loaded');">
|
||||
<script>
|
||||
initUI();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
13
browser/devtools/profiler/cleopatra/css/devtools.css
Normal file
13
browser/devtools/profiler/cleopatra/css/devtools.css
Normal file
@ -0,0 +1,13 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#mainarea > .controlPane {
|
||||
font-size: 120%;
|
||||
padding-top: 75px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#stopWrapper {
|
||||
display: none;
|
||||
}
|
245
browser/devtools/profiler/cleopatra/css/tree.css
Executable file
245
browser/devtools/profiler/cleopatra/css/tree.css
Executable file
@ -0,0 +1,245 @@
|
||||
/* 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;
|
||||
-webkit-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: -moz-linear-gradient(#FFF 45%, #EEE 60%);
|
||||
background: -webkit-linear-gradient(#FFF 45%, #EEE 60%);
|
||||
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: -moz-linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF);
|
||||
background: -webkit-linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF);
|
||||
background: linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF);
|
||||
background-size: 100px 32px;
|
||||
}
|
||||
|
||||
.leftColumnBackground {
|
||||
background: -moz-linear-gradient(left, transparent, transparent 98px, #CCC 98px, #CCC 99px, transparent 99px),
|
||||
-moz-linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF);
|
||||
background: -webkit-linear-gradient(left, transparent, transparent 98px, #CCC 98px, #CCC 99px, transparent 99px),
|
||||
-webkit-linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF);
|
||||
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;
|
||||
}
|
344
browser/devtools/profiler/cleopatra/css/ui.css
Executable file
344
browser/devtools/profiler/cleopatra/css/ui.css
Executable file
@ -0,0 +1,344 @@
|
||||
/* 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),
|
||||
-moz-linear-gradient(rgba(255,255,255,.5),rgba(255,255,255,.2));
|
||||
background-image: url(../images/noise.png),
|
||||
-webkit-linear-gradient(rgba(255,255,255,.5),rgba(255,255,255,.2));
|
||||
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 {
|
||||
-webkit-animation: darken 300ms cubic-bezier(0, 0, 1, 0);
|
||||
-moz-animation: darken 300ms cubic-bezier(0, 0, 1, 0);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.finishedProfilePane {
|
||||
-webkit-animation: appear 300ms ease-out;
|
||||
-moz-animation: appear 300ms ease-out;
|
||||
}
|
||||
|
||||
.breadcrumbTrail {
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 29px;
|
||||
left: 0;
|
||||
background: -moz-linear-gradient(#FFF 50%, #F3F3F3 55%);
|
||||
background: -webkit-linear-gradient(#FFF 50%, #F3F3F3 55%);
|
||||
background: linear-gradient(#FFF 50%, #F3F3F3 55%);
|
||||
border-bottom: 1px solid #CCC;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.breadcrumbTrailItem {
|
||||
background: -moz-linear-gradient(#FFF 50%, #F3F3F3 55%);
|
||||
background: -webkit-linear-gradient(#FFF 50%, #F3F3F3 55%);
|
||||
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;
|
||||
-webkit-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;
|
||||
}
|
||||
@-webkit-keyframes slide-out {
|
||||
from {
|
||||
margin-left: -270px;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
margin-left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@-moz-keyframes slide-out {
|
||||
from {
|
||||
margin-left: -270px;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
margin-left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.breadcrumbTrailItem:not(:first-child) {
|
||||
-moz-animation: slide-out;
|
||||
-moz-animation-duration: 400ms;
|
||||
-moz-animation-timing-function: ease-out;
|
||||
-webkit-animation: slide-out;
|
||||
-webkit-animation-duration: 400ms;
|
||||
-webkit-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 {
|
||||
-moz-transition: 400ms ease-out;
|
||||
-moz-transition-property: opacity, margin-left;
|
||||
-webkit-transition: 400ms ease-out;
|
||||
-webkit-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 {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
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: -moz-linear-gradient(#EEE, #CCC);
|
||||
background: -webkit-linear-gradient(#EEE, #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: -moz-linear-gradient(#4B91D7 1px, #5FA9E4 1px, #5FA9E4 2px, #58A0DE 3px, #2B70C7 39px, #2763B4 39px);
|
||||
background: -webkit-linear-gradient(#4B91D7 1px, #5FA9E4 1px, #5FA9E4 2px, #58A0DE 3px, #2B70C7 39px, #2763B4 39px);
|
||||
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);
|
||||
-moz-transition: 200ms ease-in-out;
|
||||
-moz-transition-property: visibility, opacity;
|
||||
-webkit-transition: 200ms ease-in-out;
|
||||
-webkit-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 {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
.videoPane {
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
}
|
||||
.video {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
27
browser/devtools/profiler/cleopatra/images/circlearrow.svg
Executable file
27
browser/devtools/profiler/cleopatra/images/circlearrow.svg
Executable file
@ -0,0 +1,27 @@
|
||||
<!-- 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>
|
After Width: | Height: | Size: 1.1 KiB |
0
browser/devtools/profiler/cleopatra/images/filter.png
Executable file
0
browser/devtools/profiler/cleopatra/images/filter.png
Executable file
0
browser/devtools/profiler/cleopatra/images/noise.png
Executable file
0
browser/devtools/profiler/cleopatra/images/noise.png
Executable file
0
browser/devtools/profiler/cleopatra/images/showall.png
Executable file
0
browser/devtools/profiler/cleopatra/images/showall.png
Executable file
23
browser/devtools/profiler/cleopatra/images/throbber.svg
Executable file
23
browser/devtools/profiler/cleopatra/images/throbber.svg
Executable file
@ -0,0 +1,23 @@
|
||||
<!-- 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>
|
After Width: | Height: | Size: 1.7 KiB |
32
browser/devtools/profiler/cleopatra/images/treetwisty.svg
Executable file
32
browser/devtools/profiler/cleopatra/images/treetwisty.svg
Executable file
@ -0,0 +1,32 @@
|
||||
<!-- 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>
|
After Width: | Height: | Size: 1.2 KiB |
185
browser/devtools/profiler/cleopatra/js/ProgressReporter.js
Executable file
185
browser/devtools/profiler/cleopatra/js/ProgressReporter.js
Executable file
@ -0,0 +1,185 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
};
|
104
browser/devtools/profiler/cleopatra/js/devtools.js
Normal file
104
browser/devtools/profiler/cleopatra/js/devtools.js
Normal file
@ -0,0 +1,104 @@
|
||||
/* 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/. */
|
||||
|
||||
var gInstanceUID;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* - start, when user wants to start profiling.
|
||||
* - stop, when user wants to stop profiling.
|
||||
*/
|
||||
function notifyParent(status) {
|
||||
if (!gInstanceUID) {
|
||||
gInstanceUID = window.location.search.substr(1);
|
||||
}
|
||||
|
||||
window.parent.postMessage({
|
||||
uid: gInstanceUID,
|
||||
status: status
|
||||
}, "*");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 msg = JSON.parse(event.data);
|
||||
|
||||
if (msg.task === "onStarted") {
|
||||
start.style.display = "none";
|
||||
start.querySelector("button").removeAttribute("disabled");
|
||||
stop.style.display = "inline";
|
||||
} else if (msg.task === "onStopped") {
|
||||
stop.style.display = "none";
|
||||
stop.querySelector("button").removeAttribute("disabled");
|
||||
start.style.display = "inline";
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", onParentMessage);
|
||||
|
||||
/**
|
||||
* Main entry point. This function initializes Cleopatra
|
||||
* in the light mode and creates all the UI we need.
|
||||
*/
|
||||
function initUI() {
|
||||
gLightMode = true;
|
||||
gJavaScriptOnly = true;
|
||||
|
||||
var container = document.createElement("div");
|
||||
container.id = "ui";
|
||||
|
||||
gMainArea = document.createElement("div");
|
||||
gMainArea.id = "mainarea";
|
||||
|
||||
container.appendChild(gMainArea);
|
||||
document.body.appendChild(container);
|
||||
|
||||
var startButton = document.createElement("button");
|
||||
startButton.innerHTML = "Start";
|
||||
startButton.addEventListener("click", function (event) {
|
||||
event.target.setAttribute("disabled", true);
|
||||
notifyParent("start");
|
||||
}, false);
|
||||
|
||||
var stopButton = document.createElement("button");
|
||||
stopButton.innerHTML = "Stop";
|
||||
stopButton.addEventListener("click", function (event) {
|
||||
event.target.setAttribute("disabled", true);
|
||||
notifyParent("stop");
|
||||
}, false);
|
||||
|
||||
var controlPane = document.createElement("div");
|
||||
controlPane.className = "controlPane";
|
||||
controlPane.innerHTML =
|
||||
"<p id='startWrapper'>Click <span class='btn'></span> to start profiling.</p>" +
|
||||
"<p id='stopWrapper'>Click <span class='btn'></span> to stop profiling.</p>";
|
||||
|
||||
controlPane.querySelector("#startWrapper > span.btn").appendChild(startButton);
|
||||
controlPane.querySelector("#stopWrapper > span.btn").appendChild(stopButton);
|
||||
|
||||
gMainArea.appendChild(controlPane);
|
||||
}
|
275
browser/devtools/profiler/cleopatra/js/parser.js
Executable file
275
browser/devtools/profiler/cleopatra/js/parser.js
Executable file
@ -0,0 +1,275 @@
|
||||
/* 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/. */
|
||||
|
||||
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 (readTime > 10)
|
||||
console.log("reading data from worker message: " + readTime + "ms");
|
||||
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;
|
||||
if (true || postTime > 10)
|
||||
console.log("posting message to worker: " + postTime + "ms");
|
||||
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;
|
||||
if (postTime > 10)
|
||||
console.log("posting message to worker: " + postTime + "ms");
|
||||
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) {
|
||||
console.log("profile num chars: " + data.length);
|
||||
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;
|
||||
},
|
||||
};
|
1499
browser/devtools/profiler/cleopatra/js/parserWorker.js
Executable file
1499
browser/devtools/profiler/cleopatra/js/parserWorker.js
Executable file
File diff suppressed because it is too large
Load Diff
641
browser/devtools/profiler/cleopatra/js/tree.js
Executable file
641
browser/devtools/profiler/cleopatra/js/tree.js
Executable file
@ -0,0 +1,641 @@
|
||||
/* 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/. */
|
||||
|
||||
var kMaxChunkDuration = 30; // ms
|
||||
|
||||
var escape = document.createElement('textarea');
|
||||
|
||||
function escapeHTML(html) {
|
||||
escape.innerHTML = html;
|
||||
return escape.innerHTML;
|
||||
}
|
||||
|
||||
function unescapeHTML(html) {
|
||||
escape.innerHTML = html;
|
||||
return escape.value;
|
||||
}
|
||||
|
||||
RegExp.escape = function(text) {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
}
|
||||
|
||||
var requestAnimationFrame_timeout = null;
|
||||
var requestAnimationFrame = window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function(callback, element) {
|
||||
window.setTimeout(callback, 1000 / 60);
|
||||
};
|
||||
|
||||
var cancelAnimationFrame = window.webkitCancelAnimationFrame ||
|
||||
window.mozCancelAnimationFrame ||
|
||||
window.oCancelAnimationFrame ||
|
||||
window.msCancelAnimationFrame ||
|
||||
function(callback, element) {
|
||||
if (requestAnimationFrame_timeout) {
|
||||
window.clearTimeout(requestAnimationFrame_timeout);
|
||||
requestAnimationFrame_timeout = null;
|
||||
}
|
||||
};
|
||||
|
||||
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._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;
|
||||
|
||||
var self = this;
|
||||
this._container.onkeydown = function (e) {
|
||||
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, filterByName) {
|
||||
this._busyCover.classList.remove("busy");
|
||||
this._filterByName = filterByName;
|
||||
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();
|
||||
this._select(this._horizontalScrollbox.firstChild);
|
||||
this._toggle(this._horizontalScrollbox.firstChild);
|
||||
this._container.focus();
|
||||
},
|
||||
// Provide a snapshot of the reverse selection to restore with 'invert callback'
|
||||
getReverseSelectionSnapshot: function TreeView__getReverseSelectionSnapshot(isJavascriptOnly) {
|
||||
if (!this._selectedNode)
|
||||
return;
|
||||
var 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.children && curr.children.length >= 1) {
|
||||
curr = curr.children[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, allowNonContigious) {
|
||||
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]) {
|
||||
//dump("Found: " + currNode.treeChildren[i].data.name + "\n");
|
||||
snapshot.shift();
|
||||
this._toggle(currNode, false, true);
|
||||
currNode = currNode.treeChildren[i];
|
||||
continue next_level;
|
||||
}
|
||||
}
|
||||
if (allowNonContigious === true) {
|
||||
// We need to do a Breadth-first search to find a match
|
||||
var pendingSearch = [currNode.data];
|
||||
while (pendingSearch.length > 0) {
|
||||
var node = pendingSearch.shift();
|
||||
//dump("searching: " + node.name + " for: " + snapshot[0] + "\n");
|
||||
if (!node.children)
|
||||
continue;
|
||||
for (var i = 0; i < node.children.length; i++) {
|
||||
var childNode = node.children[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() {
|
||||
this._leftColumnBackground.style.height = this._horizontalScrollbox.getBoundingClientRect().height + 'px';
|
||||
},
|
||||
_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);
|
||||
// When this item is toggled we will expand its children
|
||||
div.pendingExpand = [];
|
||||
div.treeLine = treeLine;
|
||||
div.data = data;
|
||||
div.appendChild(treeLine);
|
||||
div.treeChildren = [];
|
||||
div.treeParent = parentNode;
|
||||
if (hasChildren) {
|
||||
var parent = document.createElement("div");
|
||||
parent.className = "treeViewNodeList";
|
||||
for (var i = 0; i < data.children.length; ++i) {
|
||||
div.pendingExpand.push({parentElement: parent, parentNode: div, data: data.children[i].getData() });
|
||||
}
|
||||
div.appendChild(parent);
|
||||
}
|
||||
if (parentNode) {
|
||||
parentNode.treeChildren.push(div);
|
||||
}
|
||||
parentElement.appendChild(div);
|
||||
return div;
|
||||
},
|
||||
_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 != null && (
|
||||
node.library.toLowerCase() == "xul" ||
|
||||
node.library.toLowerCase() == "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 libName = node.library;
|
||||
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="functionName">' + nodeName + '</span>' +
|
||||
'<span class="libraryName">' + libName + '</span>' +
|
||||
'<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();
|
||||
}
|
||||
},
|
||||
_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");
|
||||
} else {
|
||||
this._resolveChildren(div, true);
|
||||
div.classList.remove("collapsed");
|
||||
}
|
||||
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];
|
||||
},
|
||||
_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._scrollIntoView(functionName, 400);
|
||||
this._fireEvent("select", li.data);
|
||||
}
|
||||
},
|
||||
_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);
|
||||
} 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);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
1683
browser/devtools/profiler/cleopatra/js/ui.js
Executable file
1683
browser/devtools/profiler/cleopatra/js/ui.js
Executable file
File diff suppressed because it is too large
Load Diff
18
browser/devtools/profiler/profiler.css
Normal file
18
browser/devtools/profiler/profiler.css
Normal file
@ -0,0 +1,18 @@
|
||||
.profile-name {
|
||||
font-size: 13px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#profiles-list > li {
|
||||
width: 180px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.splitview-nav-container button {
|
||||
color: white;
|
||||
background-clip: padding-box;
|
||||
border-bottom: 1px solid hsla(210,16%,76%,.1);
|
||||
box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25);
|
||||
-moz-padding-end: 8px;
|
||||
-moz-box-align: center;
|
||||
}
|
47
browser/devtools/profiler/profiler.xul
Normal file
47
browser/devtools/profiler/profiler.xul
Normal file
@ -0,0 +1,47 @@
|
||||
<?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/splitview.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/splitview.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/profiler.css"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<box flex="1" id="profiler-chrome" class="splitview-root">
|
||||
<box class="splitview-controller" width="180px">
|
||||
<box class="splitview-main"></box>
|
||||
|
||||
<box class="splitview-nav-container">
|
||||
<ol class="splitview-nav" id="profiles-list">
|
||||
<!-- Example:
|
||||
<li class="splitview-active" id="profile-1" data-uid="1">
|
||||
<h1 class="profile-name">Profile 1</h1>
|
||||
</li>
|
||||
-->
|
||||
</ol>
|
||||
|
||||
<spacer flex="1"/>
|
||||
|
||||
<toolbar class="devtools-toolbar" mode="full">
|
||||
<toolbarbutton id="profiler-create"
|
||||
class="devtools-toolbarbutton"
|
||||
label="New"
|
||||
disabled="true"/>
|
||||
</toolbar>
|
||||
</box> <!-- splitview-nav-container -->
|
||||
</box> <!-- splitview-controller -->
|
||||
|
||||
<box flex="1">
|
||||
<vbox flex="1" id="profiler-report">
|
||||
<!-- Example:
|
||||
<iframe id="profiler-cleo-1"
|
||||
src="devtools/cleopatra.html" flex="1"></iframe>
|
||||
-->
|
||||
</vbox>
|
||||
</box>
|
||||
</box>
|
||||
</window>
|
19
browser/devtools/profiler/test/Makefile.in
Normal file
19
browser/devtools/profiler/test/Makefile.in
Normal file
@ -0,0 +1,19 @@
|
||||
# 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/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = @relativesrcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
MOCHITEST_BROWSER_FILES = \
|
||||
browser_profiler_run.js \
|
||||
browser_profiler_controller.js \
|
||||
browser_profiler_profiles.js \
|
||||
head.js \
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
@ -0,0 +1,54 @@
|
||||
/* 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(testStart);
|
||||
});
|
||||
}
|
||||
|
||||
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 testStart() {
|
||||
gPanel.controller.start(function (err) {
|
||||
ok(!err, "Profiler started without errors");
|
||||
testActive(testStop);
|
||||
});
|
||||
}
|
||||
|
||||
function testStop() {
|
||||
gPanel.controller.stop(function (err, data) {
|
||||
ok(!err, "Profiler stopped without errors");
|
||||
ok(data, "Profiler returned some data");
|
||||
|
||||
testInactive(function () {
|
||||
tearDown(gTab, function () {
|
||||
gTab = null;
|
||||
gPanel = null;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
46
browser/devtools/profiler/test/browser_profiler_profiles.js
Normal file
46
browser/devtools/profiler/test/browser_profiler_profiles.js
Normal file
@ -0,0 +1,46 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function onSetUp(tab, browser, panel) {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
panel.once("profileCreated", onProfileCreated);
|
||||
panel.once("profileSwitched", onProfileSwitched);
|
||||
|
||||
testNewProfile();
|
||||
});
|
||||
}
|
||||
|
||||
function testNewProfile() {
|
||||
is(gPanel.profiles.size, 1, "There is only one profile");
|
||||
|
||||
let btn = gPanel.document.getElementById("profiler-create");
|
||||
ok(!btn.getAttribute("disabled"), "Create Profile button is not disabled");
|
||||
btn.click();
|
||||
}
|
||||
|
||||
function onProfileCreated(name, uid) {
|
||||
is(gPanel.profiles.size, 2, "There are two profiles now");
|
||||
ok(gPanel.activeProfile.uid !== uid, "New profile is not yet active");
|
||||
|
||||
let btn = gPanel.document.getElementById("profile-" + uid);
|
||||
ok(btn, "Profile item has been added to the sidebar");
|
||||
btn.click();
|
||||
}
|
||||
|
||||
function onProfileSwitched(name, uid) {
|
||||
ok(gPanel.activeProfile.uid === uid, "Switched to a new profile");
|
||||
|
||||
tearDown(gTab, function onTearDown() {
|
||||
gPanel = null;
|
||||
gTab = null;
|
||||
});
|
||||
}
|
90
browser/devtools/profiler/test/browser_profiler_run.js
Normal file
90
browser/devtools/profiler/test/browser_profiler_run.js
Normal file
@ -0,0 +1,90 @@
|
||||
/* 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, gAttempts = 0;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, function onSetUp(tab, browser, panel) {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
panel.once("started", onStart);
|
||||
panel.once("stopped", onStop);
|
||||
panel.once("parsed", onParsed);
|
||||
|
||||
testUI();
|
||||
});
|
||||
}
|
||||
|
||||
function attemptTearDown() {
|
||||
gAttempts += 1;
|
||||
|
||||
if (gAttempts < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
tearDown(gTab, function onTearDown() {
|
||||
gPanel = null;
|
||||
gTab = null;
|
||||
});
|
||||
}
|
||||
|
||||
function getProfileInternals() {
|
||||
let win = gPanel.activeProfile.iframe.contentWindow;
|
||||
let doc = win.document;
|
||||
|
||||
return [win, doc];
|
||||
}
|
||||
|
||||
function testUI() {
|
||||
ok(gPanel, "Profiler panel exists");
|
||||
ok(gPanel.activeProfile, "Active profile exists");
|
||||
|
||||
let [win, doc] = getProfileInternals();
|
||||
let startButton = doc.querySelector(".controlPane #startWrapper button");
|
||||
let stopButton = doc.querySelector(".controlPane #stopWrapper button");
|
||||
|
||||
ok(startButton, "Start button exists");
|
||||
ok(stopButton, "Stop button exists");
|
||||
|
||||
startButton.click();
|
||||
}
|
||||
|
||||
function onStart() {
|
||||
gPanel.controller.isActive(function (err, isActive) {
|
||||
ok(isActive, "Profiler is running");
|
||||
|
||||
let [win, doc] = getProfileInternals();
|
||||
let stopButton = doc.querySelector(".controlPane #stopWrapper button");
|
||||
|
||||
stopButton.click();
|
||||
});
|
||||
}
|
||||
|
||||
function onStop() {
|
||||
gPanel.controller.isActive(function (err, isActive) {
|
||||
ok(!isActive, "Profiler is idle");
|
||||
attemptTearDown();
|
||||
});
|
||||
}
|
||||
|
||||
function onParsed() {
|
||||
function assertSample() {
|
||||
let [win,doc] = getProfileInternals();
|
||||
let sample = doc.getElementsByClassName("samplePercentage");
|
||||
|
||||
if (sample.length <= 0) {
|
||||
return void setTimeout(assertSample, 100);
|
||||
}
|
||||
|
||||
ok(sample.length > 0, "We have some items displayed");
|
||||
is(sample[0].innerHTML, "100.0%", "First percentage is 100%");
|
||||
attemptTearDown();
|
||||
}
|
||||
|
||||
assertSample();
|
||||
}
|
68
browser/devtools/profiler/test/head.js
Normal file
68
browser/devtools/profiler/test/head.js
Normal file
@ -0,0 +1,68 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
let temp = {};
|
||||
const PROFILER_ENABLED = "devtools.profiler.enabled";
|
||||
|
||||
Cu.import("resource:///modules/devtools/Target.jsm", temp);
|
||||
let TargetFactory = temp.TargetFactory;
|
||||
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm", temp);
|
||||
let gDevTools = temp.gDevTools;
|
||||
|
||||
function loadTab(url, callback) {
|
||||
let tab = gBrowser.addTab();
|
||||
gBrowser.selectedTab = tab;
|
||||
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 closeProfiler(tab, callback) {
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let panel = gDevTools.getToolbox(target).getPanel("jsprofiler");
|
||||
panel.once("destroyed", callback);
|
||||
|
||||
gDevTools.closeToolbox(target);
|
||||
}
|
||||
|
||||
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();
|
||||
Services.prefs.setBoolPref(PROFILER_ENABLED, false);
|
||||
});
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
# 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/.
|
||||
|
||||
# LOCALIZATION NOTE These strings are used inside the Debugger
|
||||
# which is available from the Web Developer sub-menu -> 'Debugger'.
|
||||
# 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.
|
||||
# A good criteria is the language in which you'd find the best
|
||||
# documentation on web development on the web.
|
||||
|
||||
# LOCALIZATION NOTE (profiler.label):
|
||||
# This string is displayed in the title of the tab when the profiler is
|
||||
# displayed inside the developer tools window and in the Developer Tools Menu.
|
||||
profiler.label=Profiler
|
@ -35,6 +35,7 @@
|
||||
locale/browser/devtools/webConsole.dtd (%chrome/browser/devtools/webConsole.dtd)
|
||||
locale/browser/devtools/sourceeditor.properties (%chrome/browser/devtools/sourceeditor.properties)
|
||||
locale/browser/devtools/sourceeditor.dtd (%chrome/browser/devtools/sourceeditor.dtd)
|
||||
locale/browser/devtools/profiler.properties (%chrome/browser/devtools/profiler.properties)
|
||||
locale/browser/devtools/layoutview.dtd (%chrome/browser/devtools/layoutview.dtd)
|
||||
locale/browser/devtools/responsiveUI.properties (%chrome/browser/devtools/responsiveUI.properties)
|
||||
locale/browser/devtools/toolbox.dtd (%chrome/browser/devtools/toolbox.dtd)
|
||||
|
Loading…
Reference in New Issue
Block a user