Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-04-16 12:46:00 +02:00
commit 2a1251a294
49 changed files with 1337 additions and 463 deletions

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="629097847567e51095a454e7e63186a6e2ac0307"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="29fde3db031b910f1c9d1a35a3aab022872de205"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="629097847567e51095a454e7e63186a6e2ac0307"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="29fde3db031b910f1c9d1a35a3aab022872de205"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="629097847567e51095a454e7e63186a6e2ac0307"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="29fde3db031b910f1c9d1a35a3aab022872de205"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a1ba73572c713e298ae53821d000fc3a5087b5f"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="629097847567e51095a454e7e63186a6e2ac0307"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="29fde3db031b910f1c9d1a35a3aab022872de205"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="6b0721ca0e92788df30daf8f7a5fb2863544f9c8">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="629097847567e51095a454e7e63186a6e2ac0307"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="29fde3db031b910f1c9d1a35a3aab022872de205"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="629097847567e51095a454e7e63186a6e2ac0307"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="29fde3db031b910f1c9d1a35a3aab022872de205"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="629097847567e51095a454e7e63186a6e2ac0307"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="29fde3db031b910f1c9d1a35a3aab022872de205"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="629097847567e51095a454e7e63186a6e2ac0307"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="29fde3db031b910f1c9d1a35a3aab022872de205"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a1ba73572c713e298ae53821d000fc3a5087b5f"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "629097847567e51095a454e7e63186a6e2ac0307",
"git_revision": "29fde3db031b910f1c9d1a35a3aab022872de205",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "f9de193b7cb6ad0e84837d6c88f6c8c3f78875d4",
"revision": "2511908f59de62fbba1461a6dd0b23c16ba0be63",
"repo_path": "integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="629097847567e51095a454e7e63186a6e2ac0307"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="29fde3db031b910f1c9d1a35a3aab022872de205"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a1ba73572c713e298ae53821d000fc3a5087b5f"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="6b0721ca0e92788df30daf8f7a5fb2863544f9c8">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="629097847567e51095a454e7e63186a6e2ac0307"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="29fde3db031b910f1c9d1a35a3aab022872de205"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -139,6 +139,7 @@ SyncImpl.prototype = {
*/
_start: Task.async(function* () {
log.info("Starting sync");
yield this._logDiagnostics();
yield this._uploadStatusChanges();
yield this._uploadNewItems();
yield this._uploadDeletedItems();
@ -150,6 +151,43 @@ SyncImpl.prototype = {
log.info("Sync done");
}),
/**
* Phase 0 - for debugging we log some stuff about the local store before
* we start syncing.
* We only do this when the log level is "Trace" or lower as the info (a)
* may be expensive to generate, (b) generate alot of output and (c) may
* contain private information.
*/
_logDiagnostics: Task.async(function* () {
// Sadly our log is likely to have Log.Level.All, so loop over our
// appenders looking for the effective level.
let smallestLevel = log.appenders.reduce(
(prev, appender) => Math.min(prev, appender.level),
Log.Level.Error);
if (smallestLevel > Log.Level.Trace) {
return;
}
let localItems = [];
yield this.list.forEachItem(localItem => localItems.push(localItem));
log.trace("Have " + localItems.length + " local item(s)");
for (let localItem of localItems) {
// We need to use .record so we get access to a couple of the "internal" fields.
let record = localItem._record;
let redacted = {};
for (let attr of ["guid", "url", "resolvedURL", "serverLastModified", "syncStatus"]) {
redacted[attr] = record[attr];
}
log.trace(JSON.stringify(redacted));
}
// and the GUIDs of deleted items.
let deletedGuids = []
yield this.list.forEachSyncedDeletedGUID(guid => deletedGuids.push(guid));
// This might be a huge line, but that's OK.
log.trace("Have ${num} deleted item(s): ${deletedGuids}", {num: deletedGuids.length, deletedGuids});
}),
/**
* Phase 1 part 1
*
@ -196,7 +234,7 @@ SyncImpl.prototype = {
};
let batchResponse = yield this._postBatch(request);
if (batchResponse.status != 200) {
this._handleUnexpectedResponse("uploading changes", batchResponse);
this._handleUnexpectedResponse(true, "uploading changes", batchResponse);
return;
}
@ -215,7 +253,7 @@ SyncImpl.prototype = {
continue;
}
if (response.status != 200) {
this._handleUnexpectedResponse("uploading a change", response);
this._handleUnexpectedResponse(false, "uploading a change", response);
continue;
}
// Don't assume the local record and the server record aren't materially
@ -259,7 +297,7 @@ SyncImpl.prototype = {
};
let batchResponse = yield this._postBatch(request);
if (batchResponse.status != 200) {
this._handleUnexpectedResponse("uploading new items", batchResponse);
this._handleUnexpectedResponse(true, "uploading new items", batchResponse);
return;
}
@ -281,7 +319,7 @@ SyncImpl.prototype = {
log.debug("Attempting to upload a new item found the server already had it", response);
// but we still process it.
} else if (response.status != 201) {
this._handleUnexpectedResponse("uploading a new item", response);
this._handleUnexpectedResponse(false, "uploading a new item", response);
continue;
}
let item = yield this.list.itemForURL(response.body.url);
@ -320,7 +358,7 @@ SyncImpl.prototype = {
};
let batchResponse = yield this._postBatch(request);
if (batchResponse.status != 200) {
this._handleUnexpectedResponse("uploading deleted items", batchResponse);
this._handleUnexpectedResponse(true, "uploading deleted items", batchResponse);
return;
}
@ -329,7 +367,7 @@ SyncImpl.prototype = {
// A 404 means the item was already deleted on the server, which is OK.
// We still need to make sure it's deleted locally, though.
if (response.status != 200 && response.status != 404) {
this._handleUnexpectedResponse("uploading a deleted item", response);
this._handleUnexpectedResponse(false, "uploading a deleted item", response);
continue;
}
yield this._deleteItemForGUID(response.body.id);
@ -355,7 +393,7 @@ SyncImpl.prototype = {
};
let response = yield this._sendRequest(request);
if (response.status != 200) {
this._handleUnexpectedResponse("downloading modified items", response);
this._handleUnexpectedResponse(true, "downloading modified items", response);
return;
}
@ -549,8 +587,16 @@ SyncImpl.prototype = {
return bigResponse;
}),
_handleUnexpectedResponse(contextMsgFragment, response) {
_handleUnexpectedResponse(isTopLevel, contextMsgFragment, response) {
log.error(`Unexpected response ${contextMsgFragment}`, response);
// We want to throw in some cases so the sync engine knows there was an
// error and retries using the error schedule. 401 implies an auth issue
// (possibly transient, possibly not) - but things like 404 might just
// relate to a single item and need not throw. Any 5XX implies a
// (hopefully transient) server error.
if (isTopLevel && (response.status == 401 || response.status >= 500)) {
throw new Error("Sync aborted due to " + response.status + " server response.");
}
},
// TODO: Wipe this pref when user logs out.

View File

@ -119,6 +119,29 @@ let selectNode = Task.async(function*(data, inspector, reason="test") {
yield updated;
});
/**
* Takes an Inspector panel that was just created, and waits
* for a "inspector-updated" event as well as the animation inspector
* sidebar to be ready. Returns a promise once these are completed.
*
* @param {InspectorPanel} inspector
* @return {Promise}
*/
let waitForAnimationInspectorReady = Task.async(function*(inspector) {
let win = inspector.sidebar.getWindowForTab("animationinspector");
let updated = inspector.once("inspector-updated");
// In e10s, if we wait for underlying toolbox actors to
// load (by setting gDevTools.testing to true), we miss the "animationinspector-ready"
// event on the sidebar, so check to see if the iframe
// is already loaded.
let tabReady = win.document.readyState === "complete" ?
promise.resolve() :
inspector.sidebar.once("animationinspector-ready");
return promise.all([updated, tabReady]);
});
/**
* Open the toolbox, with the inspector tool visible and the animationinspector
* sidebar selected.
@ -129,18 +152,19 @@ let openAnimationInspector = Task.async(function*() {
info("Opening the toolbox with the inspector selected");
let toolbox = yield gDevTools.showToolbox(target, "inspector");
yield waitForToolboxFrameFocus(toolbox);
info("Switching to the animationinspector");
let inspector = toolbox.getPanel("inspector");
let initPromises = [
inspector.once("inspector-updated"),
inspector.sidebar.once("animationinspector-ready")
];
let panelReady = waitForAnimationInspectorReady(inspector);
info("Waiting for toolbox focus");
yield waitForToolboxFrameFocus(toolbox);
inspector.sidebar.select("animationinspector");
info("Waiting for the inspector and sidebar to be ready");
yield promise.all(initPromises);
yield panelReady;
let win = inspector.sidebar.getWindowForTab("animationinspector");
let {AnimationsController, AnimationsPanel} = win;

View File

@ -14,15 +14,12 @@ Cu.import("resource://gre/modules/devtools/Loader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "promise",
"resource://gre/modules/Promise.jsm", "Promise");
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/devtools/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
"resource://gre/modules/devtools/dbg-client.jsm");
@ -1212,16 +1209,6 @@ let gDevToolsBrowser = {
}
},
/**
* Connects to the SPS profiler when the developer tools are open. This is
* necessary because of the WebConsole's `profile` and `profileEnd` methods.
*/
_connectToProfiler: function DT_connectToProfiler(event, toolbox) {
let SharedPerformanceUtils = devtools.require("devtools/performance/front");
let connection = SharedPerformanceUtils.getPerformanceActorsConnection(toolbox.target);
connection.open();
},
/**
* Remove the menuitem for a tool to all open browser windows.
*
@ -1330,7 +1317,6 @@ let gDevToolsBrowser = {
* All browser windows have been closed, tidy up remaining objects.
*/
destroy: function() {
gDevTools.off("toolbox-ready", gDevToolsBrowser._connectToProfiler);
Services.prefs.removeObserver("devtools.", gDevToolsBrowser);
Services.obs.removeObserver(gDevToolsBrowser.destroy, "quit-application");
},
@ -1351,7 +1337,6 @@ gDevTools.on("tool-unregistered", function(ev, toolId) {
});
gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox);
gDevTools.on("toolbox-ready", gDevToolsBrowser._connectToProfiler);
gDevTools.on("toolbox-destroyed", gDevToolsBrowser._updateMenuCheckbox);
Services.obs.addObserver(gDevToolsBrowser.destroy, "quit-application", false);

View File

@ -51,6 +51,7 @@ loader.lazyGetter(this, "toolboxStrings", () => {
loader.lazyGetter(this, "Selection", () => require("devtools/framework/selection").Selection);
loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront);
loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/toolkit/DevToolsUtils");
loader.lazyRequireGetter(this, "getPerformanceActorsConnection", "devtools/performance/front", true);
XPCOMUtils.defineLazyGetter(this, "screenManager", () => {
return Cc["@mozilla.org/gfx/screenmanager;1"].getService(Ci.nsIScreenManager);
@ -310,80 +311,80 @@ Toolbox.prototype = {
/**
* Open the toolbox
*/
open: function() {
let deferred = promise.defer();
return this._host.create().then(iframe => {
let deferred = promise.defer();
let domReady = () => {
this.isReady = true;
let framesPromise = this._listFrames();
this.closeButton = this.doc.getElementById("toolbox-close");
this.closeButton.addEventListener("command", this.destroy, true);
gDevTools.on("pref-changed", this._prefChanged);
let framesMenu = this.doc.getElementById("command-button-frames");
framesMenu.addEventListener("command", this.selectFrame, true);
this._buildDockButtons();
this._buildOptions();
this._buildTabs();
this._applyCacheSettings();
this._applyServiceWorkersTestingSettings();
this._addKeysToWindow();
this._addReloadKeys();
this._addHostListeners();
if (this._hostOptions && this._hostOptions.zoom === false) {
this._disableZoomKeys();
} else {
this._addZoomKeys();
this._loadInitialZoom();
}
this.webconsolePanel = this.doc.querySelector("#toolbox-panel-webconsole");
this.webconsolePanel.height =
Services.prefs.getIntPref(SPLITCONSOLE_HEIGHT_PREF);
this.webconsolePanel.addEventListener("resize",
this._saveSplitConsoleHeight);
let buttonsPromise = this._buildButtons();
this._pingTelemetry();
this.selectTool(this._defaultToolId).then(panel => {
// Wait until the original tool is selected so that the split
// console input will receive focus.
let splitConsolePromise = promise.resolve();
if (Services.prefs.getBoolPref(SPLITCONSOLE_ENABLED_PREF)) {
splitConsolePromise = this.openSplitConsole();
}
promise.all([
splitConsolePromise,
buttonsPromise,
framesPromise
]).then(() => {
this.emit("ready");
deferred.resolve();
}, deferred.reject);
});
};
open: function () {
return Task.spawn(function*() {
let iframe = yield this._host.create();
let domReady = promise.defer();
// Load the toolbox-level actor fronts and utilities now
this._target.makeRemote().then(() => {
iframe.setAttribute("src", this._URL);
iframe.setAttribute("aria-label", toolboxStrings("toolbox.label"));
let domHelper = new DOMHelpers(iframe.contentWindow);
domHelper.onceDOMReady(domReady);
});
yield this._target.makeRemote();
iframe.setAttribute("src", this._URL);
iframe.setAttribute("aria-label", toolboxStrings("toolbox.label"));
let domHelper = new DOMHelpers(iframe.contentWindow);
domHelper.onceDOMReady(() => domReady.resolve());
return deferred.promise;
}).then(null, console.error.bind(console));
yield domReady.promise;
this.isReady = true;
let framesPromise = this._listFrames();
this.closeButton = this.doc.getElementById("toolbox-close");
this.closeButton.addEventListener("command", this.destroy, true);
gDevTools.on("pref-changed", this._prefChanged);
let framesMenu = this.doc.getElementById("command-button-frames");
framesMenu.addEventListener("command", this.selectFrame, true);
this._buildDockButtons();
this._buildOptions();
this._buildTabs();
this._applyCacheSettings();
this._applyServiceWorkersTestingSettings();
this._addKeysToWindow();
this._addReloadKeys();
this._addHostListeners();
if (this._hostOptions && this._hostOptions.zoom === false) {
this._disableZoomKeys();
} else {
this._addZoomKeys();
this._loadInitialZoom();
}
this.webconsolePanel = this.doc.querySelector("#toolbox-panel-webconsole");
this.webconsolePanel.height = Services.prefs.getIntPref(SPLITCONSOLE_HEIGHT_PREF);
this.webconsolePanel.addEventListener("resize", this._saveSplitConsoleHeight);
let buttonsPromise = this._buildButtons();
this._pingTelemetry();
let panel = yield this.selectTool(this._defaultToolId);
// Wait until the original tool is selected so that the split
// console input will receive focus.
let splitConsolePromise = promise.resolve();
if (Services.prefs.getBoolPref(SPLITCONSOLE_ENABLED_PREF)) {
splitConsolePromise = this.openSplitConsole();
}
yield promise.all([
splitConsolePromise,
buttonsPromise,
framesPromise
]);
let profilerReady = this._connectProfiler();
// Only wait for the profiler initialization during tests. Otherwise,
// lazily load this. This is to intercept console.profile calls; the performance
// tools will explicitly wait for the connection opening when opened.
if (gDevTools.testing) {
yield profilerReady;
}
this.emit("ready");
}.bind(this)).then(null, console.error.bind(console));
},
_pingTelemetry: function() {
@ -1690,6 +1691,7 @@ Toolbox.prototype = {
this._target.off("frame-update", this._updateFrames);
this.off("select", this._refreshHostTitle);
this.off("host-changed", this._refreshHostTitle);
this.off("ready", this._showDevEditionPromo);
gDevTools.off("tool-registered", this._toolRegistered);
gDevTools.off("tool-unregistered", this._toolUnregistered);
@ -1735,6 +1737,9 @@ Toolbox.prototype = {
}
}));
// Destroy the profiler connection
outstanding.push(this._disconnectProfiler());
// We need to grab a reference to win before this._host is destroyed.
let win = this.frame.ownerGlobal;
@ -1815,5 +1820,39 @@ Toolbox.prototype = {
}
let window = this.frame.contentWindow;
showDoorhanger({ window, type: "deveditionpromo" });
}
},
getPerformanceActorsConnection: function() {
if (!this._performanceConnection) {
this._performanceConnection = getPerformanceActorsConnection(this.target);
}
return this._performanceConnection;
},
/**
* Connects to the SPS profiler when the developer tools are open. This is
* necessary because of the WebConsole's `profile` and `profileEnd` methods.
*/
_connectProfiler: Task.async(function*() {
// If target does not have profiler actor (addons), do not
// even register the shared performance connection.
if (!this.target.hasActor("profiler")) {
return;
}
yield this.getPerformanceActorsConnection().open();
// Emit an event when connected, but don't wait on startup for this.
this.emit("profiler-connected");
}),
/**
* Disconnects the underlying Performance Actor Connection.
*/
_disconnectProfiler: Task.async(function*() {
if (!this._performanceConnection) {
return;
}
yield this._performanceConnection.destroy();
this._performanceConnection = null;
}),
};

View File

@ -6,6 +6,7 @@
const { Cc, Ci, Cu, Cr } = require("chrome");
const { Task } = require("resource://gre/modules/Task.jsm");
const { extend } = require("sdk/util/object");
const { RecordingModel } = require("devtools/performance/recording-model");
loader.lazyRequireGetter(this, "Services");
loader.lazyRequireGetter(this, "promise");
@ -26,10 +27,22 @@ loader.lazyImporter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
loader.lazyImporter(this, "clearTimeout",
"resource://gre/modules/Timer.jsm");
loader.lazyImporter(this, "Promise",
"resource://gre/modules/Promise.jsm");
// How often do we pull allocation sites from the memory actor.
const DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT = 200; // ms
// Events to pipe from PerformanceActorsConnection to the PerformanceFront
const CONNECTION_PIPE_EVENTS = [
"console-profile-start", "console-profile-ending", "console-profile-end",
"timeline-data", "profiler-already-active", "profiler-activated"
];
// Events to listen to from the profiler actor
const PROFILER_EVENTS = ["console-api-profiler", "profiler-stopped"];
/**
* A cache of all PerformanceActorsConnection instances.
* The keys are Target objects.
@ -71,6 +84,16 @@ function PerformanceActorsConnection(target) {
this._target = target;
this._client = this._target.client;
this._request = this._request.bind(this);
this._pendingConsoleRecordings = [];
this._sitesPullTimeout = 0;
this._recordings = [];
this._onTimelineMarkers = this._onTimelineMarkers.bind(this);
this._onTimelineFrames = this._onTimelineFrames.bind(this);
this._onTimelineMemory = this._onTimelineMemory.bind(this);
this._onTimelineTicks = this._onTimelineTicks.bind(this);
this._onProfilerEvent = this._onProfilerEvent.bind(this);
this._pullAllocationSites = this._pullAllocationSites.bind(this);
Services.obs.notifyObservers(null, "performance-actors-connection-created", null);
}
@ -89,10 +112,14 @@ PerformanceActorsConnection.prototype = {
* A promise that is resolved once the connection is established.
*/
open: Task.async(function*() {
if (this._connected) {
return;
if (this._connecting) {
return this._connecting.promise;
}
// Create a promise that gets resolved upon connecting, so that
// other attempts to open the connection use the same resolution promise
this._connecting = Promise.defer();
// Local debugging needs to make the target remote.
yield this._target.makeRemote();
@ -104,8 +131,11 @@ PerformanceActorsConnection.prototype = {
yield this._connectTimelineActor();
yield this._connectMemoryActor();
yield this._registerListeners();
this._connected = true;
this._connecting.resolve();
Services.obs.notifyObservers(null, "performance-actors-connection-opened", null);
}),
@ -113,7 +143,14 @@ PerformanceActorsConnection.prototype = {
* Destroys this connection.
*/
destroy: Task.async(function*() {
if (this._connecting && !this._connected) {
console.warn("Attempting to destroy SharedPerformanceActorsConnection before initialization completion. If testing, ensure `gDevTools.testing` is set.");
}
yield this._unregisterListeners();
yield this._disconnectActors();
this._memory = this._timeline = this._profiler = this._target = this._client = null;
this._connected = false;
}),
@ -163,6 +200,35 @@ PerformanceActorsConnection.prototype = {
}
}),
/**
* Registers listeners on events from the underlying
* actors, so the connection can handle them.
*/
_registerListeners: Task.async(function*() {
// Pipe events from TimelineActor to the PerformanceFront
this._timeline.on("markers", this._onTimelineMarkers);
this._timeline.on("frames", this._onTimelineFrames);
this._timeline.on("memory", this._onTimelineMemory);
this._timeline.on("ticks", this._onTimelineTicks);
// Register events on the profiler actor to hook into `console.profile*` calls.
yield this._request("profiler", "registerEventNotifications", { events: PROFILER_EVENTS });
this._client.addListener("eventNotification", this._onProfilerEvent);
}),
/**
* Unregisters listeners on events on the underlying actors.
*/
_unregisterListeners: Task.async(function*() {
this._timeline.off("markers", this._onTimelineMarkers);
this._timeline.off("frames", this._onTimelineFrames);
this._timeline.off("memory", this._onTimelineMemory);
this._timeline.off("ticks", this._onTimelineTicks);
yield this._request("profiler", "unregisterEventNotifications", { events: PROFILER_EVENTS });
this._client.removeListener("eventNotification", this._onProfilerEvent);
}),
/**
* Closes the connections to non-profiler actors.
*/
@ -204,75 +270,196 @@ PerformanceActorsConnection.prototype = {
if (actor == "memory") {
return this._memory[method].apply(this._memory, args);
}
}
};
/**
* A thin wrapper around a shared PerformanceActorsConnection for the parent target.
* Handles manually starting and stopping a recording.
*
* @param PerformanceActorsConnection connection
* The shared instance for the parent target.
*/
function PerformanceFront(connection) {
EventEmitter.decorate(this);
this._request = connection._request;
// Pipe events from TimelineActor to the PerformanceFront
connection._timeline.on("markers", markers => this.emit("markers", markers));
connection._timeline.on("frames", (delta, frames) => this.emit("frames", delta, frames));
connection._timeline.on("memory", (delta, measurement) => this.emit("memory", delta, measurement));
connection._timeline.on("ticks", (delta, timestamps) => this.emit("ticks", delta, timestamps));
// Set when mocks are being used
this._usingMockMemory = connection._usingMockMemory;
this._usingMockTimeline = connection._usingMockTimeline;
this._pullAllocationSites = this._pullAllocationSites.bind(this);
this._sitesPullTimeout = 0;
}
PerformanceFront.prototype = {
},
/**
* Manually begins a recording session.
* Invoked whenever a registered event was emitted by the profiler actor.
*
* @param object response
* The data received from the backend.
*/
_onProfilerEvent: function (_, { topic, subject, details }) {
if (topic === "console-api-profiler") {
if (subject.action === "profile") {
this._onConsoleProfileStart(details);
} else if (subject.action === "profileEnd") {
this._onConsoleProfileEnd(details);
}
} else if (topic === "profiler-stopped") {
this._onProfilerUnexpectedlyStopped();
}
},
/**
* TODO handle bug 1144438
*/
_onProfilerUnexpectedlyStopped: function () {
},
/**
* Invoked whenever `console.profile` is called.
*
* @param string profileLabel
* The provided string argument if available; undefined otherwise.
* @param number currentTime
* The time (in milliseconds) when the call was made, relative to when
* the nsIProfiler module was started.
*/
_onConsoleProfileStart: Task.async(function *({ profileLabel, currentTime: startTime }) {
let recordings = this._recordings;
// Abort if a profile with this label already exists.
if (recordings.find(e => e.getLabel() === profileLabel)) {
return;
}
// Ensure the performance front is set up and ready.
// Slight performance overhead for this, should research some more.
// This is to ensure that there is a front to receive the events for
// the console profiles.
yield gDevTools.getToolbox(this._target).loadTool("performance");
let model = yield this.startRecording(extend(getRecordingModelPrefs(), {
console: true,
label: profileLabel
}));
this.emit("console-profile-start", model);
}),
/**
* Invoked whenever `console.profileEnd` is called.
*
* @param object profilerData
* The dump of data from the profiler triggered by this console.profileEnd call.
*/
_onConsoleProfileEnd: Task.async(function *(profilerData) {
let pending = this._recordings.filter(r => r.isConsole() && r.isRecording());
if (pending.length === 0) {
return;
}
let model;
// Try to find the corresponding `console.profile` call if
// a label was used in profileEnd(). If no matches, abort.
if (profilerData.profileLabel) {
model = pending.find(e => e.getLabel() === profilerData.profileLabel);
}
// If no label supplied, pop off the most recent pending console recording
else {
model = pending[pending.length - 1];
}
// If `profileEnd()` was called with a label, and there are no matching
// sessions, abort.
if (!model) {
Cu.reportError("console.profileEnd() called with label that does not match a recording.");
return;
}
this.emit("console-profile-ending", model);
yield this.stopRecording(model);
this.emit("console-profile-end", model);
}),
/**
* Handlers for TimelineActor events. All pipe to `_onTimelineData`
* with the appropriate event name.
*/
_onTimelineMarkers: function (markers) { this._onTimelineData("markers", markers); },
_onTimelineFrames: function (delta, frames) { this._onTimelineData("frames", delta, frames); },
_onTimelineMemory: function (delta, measurement) { this._onTimelineData("memory", delta, measurement); },
_onTimelineTicks: function (delta, timestamps) { this._onTimelineData("ticks", delta, timestamps); },
/**
* Called whenever there is timeline data of any of the following types:
* - markers
* - frames
* - memory
* - ticks
* - allocations
*
* Populate our internal store of recordings for all currently recording sessions.
*/
_onTimelineData: function (...data) {
this._recordings.forEach(e => e.addTimelineData.apply(e, data));
this.emit("timeline-data", ...data);
},
/**
* Begins a recording session
*
* @param object options
* An options object to pass to the actors. Supported properties are
* `withTicks`, `withMemory` and `withAllocations`.
* `withTicks`, `withMemory` and `withAllocations`, `probability`, and `maxLogLength`.
* @return object
* A promise that is resolved once recording has started.
*/
startRecording: Task.async(function*(options = {}) {
let model = new RecordingModel(options);
// All actors are started asynchronously over the remote debugging protocol.
// Get the corresponding start times from each one of them.
let profilerStartTime = yield this._startProfiler();
let timelineStartTime = yield this._startTimeline(options);
let memoryStartTime = yield this._startMemory(options);
return {
let data = {
profilerStartTime,
timelineStartTime,
memoryStartTime
};
// Signify to the model that the recording has started,
// populate with data and store the recording model here.
model.populate(data);
this._recordings.push(model);
return model;
}),
/**
* Manually ends the current recording session.
* Manually ends the recording session for the corresponding RecordingModel.
*
* @param object options
* @see PerformanceFront.prototype.startRecording
* @return object
* A promise that is resolved once recording has stopped,
* with the profiler and memory data, along with all the end times.
* @param RecordingModel model
* The corresponding RecordingModel that belongs to the recording session wished to stop.
* @return RecordingModel
* Returns the same model, populated with the profiling data.
*/
stopRecording: Task.async(function*(options = {}) {
let memoryEndTime = yield this._stopMemory(options);
let timelineEndTime = yield this._stopTimeline(options);
let profilerData = yield this._request("profiler", "getProfile");
stopRecording: Task.async(function*(model) {
// If model isn't in the PerformanceActorsConnections internal store,
// then do nothing.
if (!this._recordings.includes(model)) {
return;
}
return {
// Currently there are two ways profiles stop recording. Either manually in the
// performance tool, or via console.profileEnd. Once a recording is done,
// we want to deliver the model to the performance tool (either as a return
// from the PerformanceFront or via `console-profile-end` event) and then
// remove it from the internal store.
//
// In the case where a console.profile is generated via the console (so the tools are
// open), we initialize the Performance tool so it can listen to those events.
this._recordings.splice(this._recordings.indexOf(model), 1);
let config = model.getConfiguration();
let profilerData = yield this._request("profiler", "getProfile");
let memoryEndTime = Date.now();
let timelineEndTime = Date.now();
// Only if there are no more sessions recording do we stop
// the underlying memory and timeline actors. If we're still recording,
// juse use Date.now() for the memory and timeline end times, as those
// are only used in tests.
if (!this.isRecording()) {
memoryEndTime = yield this._stopMemory(config);
timelineEndTime = yield this._stopTimeline(config);
}
// Set the results on the RecordingModel itself.
model._onStopRecording({
// Data available only at the end of a recording.
profile: profilerData.profile,
@ -280,9 +467,21 @@ PerformanceFront.prototype = {
profilerEndTime: profilerData.currentTime,
timelineEndTime: timelineEndTime,
memoryEndTime: memoryEndTime
};
});
return model;
}),
/**
* Checks all currently stored recording models and returns a boolean
* if there is a session currently being recorded.
*
* @return Boolean
*/
isRecording: function () {
return this._recordings.some(recording => recording.isRecording());
},
/**
* Starts the profiler actor, if necessary.
*/
@ -389,7 +588,8 @@ PerformanceFront.prototype = {
}
let memoryData = yield this._request("memory", "getAllocations");
this.emit("allocations", {
this._onTimelineData("allocations", {
sites: memoryData.allocations,
timestamps: memoryData.allocationsTimestamps,
frames: memoryData.frames,
@ -402,6 +602,60 @@ PerformanceFront.prototype = {
deferred.resolve();
}),
toString: () => "[object PerformanceActorsConnection]"
};
/**
* A thin wrapper around a shared PerformanceActorsConnection for the parent target.
* Handles manually starting and stopping a recording.
*
* @param PerformanceActorsConnection connection
* The shared instance for the parent target.
*/
function PerformanceFront(connection) {
EventEmitter.decorate(this);
this._connection = connection;
this._request = connection._request;
// Set when mocks are being used
this._usingMockMemory = connection._usingMockMemory;
this._usingMockTimeline = connection._usingMockTimeline;
// Pipe the console profile events from the connection
// to the front so that the UI can listen.
CONNECTION_PIPE_EVENTS.forEach(eventName => this._connection.on(eventName, () => this.emit.apply(this, arguments)));
}
PerformanceFront.prototype = {
/**
* Manually begins a recording session and creates a RecordingModel.
* Calls the underlying PerformanceActorsConnection's startRecording method.
*
* @param object options
* An options object to pass to the actors. Supported properties are
* `withTicks`, `withMemory` and `withAllocations`, `probability` and `maxLogLength`.
* @return object
* A promise that is resolved once recording has started.
*/
startRecording: function (options) {
return this._connection.startRecording(options);
},
/**
* Manually ends the recording session for the corresponding RecordingModel.
* Calls the underlying PerformanceActorsConnection's
*
* @param RecordingModel model
* The corresponding RecordingModel that belongs to the recording session wished to stop.
* @return RecordingModel
* Returns the same model, populated with the profiling data.
*/
stopRecording: function (model) {
return this._connection.stopRecording(model);
},
/**
* Returns an object indicating if mock actors are being used or not.
*/
@ -423,5 +677,18 @@ function listTabs(client) {
return deferred.promise;
}
/**
* Creates an object of configurations based off of preferences for a RecordingModel.
*/
function getRecordingModelPrefs () {
return {
withMemory: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
withTicks: Services.prefs.getBoolPref("devtools.performance.ui.enable-framerate"),
withAllocations: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
allocationsSampleProbability: +Services.prefs.getCharPref("devtools.performance.memory.sample-probability"),
allocationsMaxLogLength: Services.prefs.getIntPref("devtools.performance.memory.max-log-length")
};
}
exports.getPerformanceActorsConnection = target => SharedPerformanceActors.forTarget(target);
exports.PerformanceFront = PerformanceFront;

View File

@ -4,6 +4,7 @@
"use strict";
const { Cc, Ci, Cu, Cr } = require("chrome");
const { Task } = require("resource://gre/modules/Task.jsm");
loader.lazyRequireGetter(this, "PerformanceIO",
"devtools/performance/io", true);
@ -17,9 +18,8 @@ loader.lazyRequireGetter(this, "RecordingUtils",
*/
const RecordingModel = function (options={}) {
this._front = options.front;
this._performance = options.performance;
this._label = options.label || "";
this._console = options.console || false;
this._configuration = {
withTicks: options.withTicks || false,
@ -32,6 +32,7 @@ const RecordingModel = function (options={}) {
RecordingModel.prototype = {
// Private fields, only needed when a recording is started or stopped.
_console: false,
_imported: false,
_recording: false,
_profilerStartTime: 0,
@ -81,16 +82,16 @@ RecordingModel.prototype = {
}),
/**
* Starts recording with the PerformanceFront.
* Sets up the instance with data from the SharedPerformanceConnection when
* starting a recording. Should only be called by SharedPerformanceConnection.
*/
startRecording: Task.async(function *() {
populate: function (info) {
// Times must come from the actor in order to be self-consistent.
// However, we also want to update the view with the elapsed time
// even when the actor is not generating data. To do this we get
// the local time and use it to compute a reasonable elapsed time.
this._localStartTime = this._performance.now();
this._localStartTime = Date.now()
let info = yield this._front.startRecording(this.getConfiguration());
this._profilerStartTime = info.profilerStartTime;
this._timelineStartTime = info.timelineStartTime;
this._memoryStartTime = info.memoryStartTime;
@ -101,13 +102,13 @@ RecordingModel.prototype = {
this._memory = [];
this._ticks = [];
this._allocations = { sites: [], timestamps: [], frames: [], counts: [] };
}),
},
/**
* Stops recording with the PerformanceFront.
* Sets results available from stopping a recording from SharedPerformanceConnection.
* Should only be called by SharedPerformanceConnection.
*/
stopRecording: Task.async(function *() {
let info = yield this._front.stopRecording(this.getConfiguration());
_onStopRecording: Task.async(function *(info) {
this._profile = info.profile;
this._duration = info.profilerEndTime - this._profilerStartTime;
this._recording = false;
@ -140,7 +141,7 @@ RecordingModel.prototype = {
// still in progress. This is needed to ensure that the view updates even
// when new data is not being generated.
if (this._recording) {
return this._performance.now() - this._localStartTime;
return Date.now() - this._localStartTime;
} else {
return this._duration;
}
@ -218,6 +219,22 @@ RecordingModel.prototype = {
return { label, duration, markers, frames, memory, ticks, allocations, profile };
},
/**
* Returns a boolean indicating whether or not this recording model
* was imported via file.
*/
isImported: function () {
return this._imported;
},
/**
* Returns a boolean indicating whether or not this recording model
* was started via a `console.profile` call.
*/
isConsole: function () {
return this._console;
},
/**
* Returns a boolean indicating whether or not this recording model
* is recording.

View File

@ -6,7 +6,7 @@
"use strict";
const {Cc, Ci, Cu, Cr} = require("chrome");
const { PerformanceFront, getPerformanceActorsConnection } = require("devtools/performance/front");
const { PerformanceFront } = require("devtools/performance/front");
Cu.import("resource://gre/modules/Task.jsm");
@ -35,7 +35,11 @@ PerformancePanel.prototype = {
this.panelWin.gToolbox = this._toolbox;
this.panelWin.gTarget = this.target;
this._connection = getPerformanceActorsConnection(this.target);
// Connection is already created in the toolbox; reuse
// the same connection.
this._connection = this.panelWin.gToolbox.getPerformanceActorsConnection();
// The toolbox will also open the connection, but attempt to open it again
// incase it's still in the process of opening.
yield this._connection.open();
this.panelWin.gFront = new PerformanceFront(this._connection);
@ -57,9 +61,6 @@ PerformancePanel.prototype = {
return;
}
// Destroy the connection to ensure packet handlers are removed from client.
yield this._connection.destroy();
yield this.panelWin.shutdownPerformance();
this.emit("destroyed");
this._destroyed = true;

View File

@ -69,6 +69,13 @@ const EVENTS = {
// Fired by the PerformanceController when the devtools theme changes.
THEME_CHANGED: "Performance:ThemeChanged",
// When the SharedPerformanceConnection handles profiles created via `console.profile()`,
// the controller handles those events and emits the below events for consumption
// by other views.
CONSOLE_RECORDING_STARTED: "Performance:ConsoleRecordingStarted",
CONSOLE_RECORDING_WILL_STOP: "Performance:ConsoleRecordingWillStop",
CONSOLE_RECORDING_STOPPED: "Performance:ConsoleRecordingStopped",
// Emitted by the PerformanceView when the state (display mode) changes,
// for example when switching between "empty", "recording" or "recorded".
// This causes certain panels to be hidden or visible.
@ -103,8 +110,6 @@ const EVENTS = {
RECORDING_IMPORTED: "Performance:RecordingImported",
RECORDING_EXPORTED: "Performance:RecordingExported",
// When the PerformanceController has new recording data
TIMELINE_DATA: "Performance:TimelineData",
// Emitted by the JITOptimizationsView when it renders new optimization
// data and clears the optimization data
@ -188,10 +193,12 @@ let PerformanceController = {
this.importRecording = this.importRecording.bind(this);
this.exportRecording = this.exportRecording.bind(this);
this.clearRecordings = this.clearRecordings.bind(this);
this._onTimelineData = this._onTimelineData.bind(this);
this._onRecordingSelectFromView = this._onRecordingSelectFromView.bind(this);
this._onPrefChanged = this._onPrefChanged.bind(this);
this._onThemeChanged = this._onThemeChanged.bind(this);
this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this);
this._onConsoleProfileEnd = this._onConsoleProfileEnd.bind(this);
this._onConsoleProfileEnding = this._onConsoleProfileEnding.bind(this);
// All boolean prefs should be handled via the OptionsView in the
// ToolbarView, so that they may be accessible via the "gear" menu.
@ -205,6 +212,9 @@ let PerformanceController = {
this._nonBooleanPrefs.registerObserver();
this._nonBooleanPrefs.on("pref-changed", this._onPrefChanged);
gFront.on("console-profile-start", this._onConsoleProfileStart);
gFront.on("console-profile-ending", this._onConsoleProfileEnding);
gFront.on("console-profile-end", this._onConsoleProfileEnd);
ToolbarView.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
@ -214,11 +224,6 @@ let PerformanceController = {
RecordingsView.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
gDevTools.on("pref-changed", this._onThemeChanged);
gFront.on("markers", this._onTimelineData); // timeline markers
gFront.on("frames", this._onTimelineData); // stack frames
gFront.on("memory", this._onTimelineData); // memory measurements
gFront.on("ticks", this._onTimelineData); // framerate
gFront.on("allocations", this._onTimelineData); // memory allocations
}),
/**
@ -228,6 +233,9 @@ let PerformanceController = {
this._nonBooleanPrefs.unregisterObserver();
this._nonBooleanPrefs.off("pref-changed", this._onPrefChanged);
gFront.off("console-profile-start", this._onConsoleProfileStart);
gFront.off("console-profile-ending", this._onConsoleProfileEnding);
gFront.off("console-profile-end", this._onConsoleProfileEnd);
ToolbarView.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
@ -237,11 +245,6 @@ let PerformanceController = {
RecordingsView.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
gDevTools.off("pref-changed", this._onThemeChanged);
gFront.off("markers", this._onTimelineData);
gFront.off("frames", this._onTimelineData);
gFront.off("memory", this._onTimelineData);
gFront.off("ticks", this._onTimelineData);
gFront.off("allocations", this._onTimelineData);
},
/**
@ -285,19 +288,20 @@ let PerformanceController = {
* when the front has started to record.
*/
startRecording: Task.async(function *() {
let recording = this._createRecording({
let options = {
withMemory: this.getOption("enable-memory"),
withTicks: this.getOption("enable-framerate"),
withAllocations: this.getOption("enable-memory"),
allocationsSampleProbability: this.getPref("memory-sample-probability"),
allocationsMaxLogLength: this.getPref("memory-max-log-length")
});
};
this.emit(EVENTS.RECORDING_WILL_START);
let recording = yield gFront.startRecording(options);
this._recordings.push(recording);
this.emit(EVENTS.RECORDING_WILL_START, recording);
yield recording.startRecording();
this.emit(EVENTS.RECORDING_STARTED, recording);
this.setCurrentRecording(recording);
}),
/**
@ -305,10 +309,10 @@ let PerformanceController = {
* when the front has stopped recording.
*/
stopRecording: Task.async(function *() {
let recording = this.getLatestRecording();
let recording = this.getLatestManualRecording();
this.emit(EVENTS.RECORDING_WILL_STOP, recording);
yield recording.stopRecording();
yield gFront.stopRecording(recording);
this.emit(EVENTS.RECORDING_STOPPED, recording);
}),
@ -331,7 +335,7 @@ let PerformanceController = {
* Emits `EVENTS.RECORDINGS_CLEARED` when complete so other components can clean up.
*/
clearRecordings: Task.async(function* () {
let latest = this.getLatestRecording();
let latest = this.getLatestManualRecording();
if (latest && latest.isRecording()) {
yield this.stopRecording();
@ -350,32 +354,17 @@ let PerformanceController = {
* The file to import the data from.
*/
importRecording: Task.async(function*(_, file) {
let recording = this._createRecording();
let recording = new RecordingModel();
this._recordings.push(recording);
yield recording.importRecording(file);
this.emit(EVENTS.RECORDING_IMPORTED, recording);
}),
/**
* Creates a new RecordingModel, fires events and stores it
* internally in the controller.
*
* @param object options
* @see PerformanceFront.prototype.startRecording
* @return RecordingModel
* The newly created recording model.
*/
_createRecording: function (options={}) {
let recording = new RecordingModel(Heritage.extend(options, {
front: gFront,
performance: window.performance
}));
this._recordings.push(recording);
return recording;
},
/**
* Sets the currently active RecordingModel.
* Sets the currently active RecordingModel. Should rarely be called directly,
* as RecordingsView handles this when manually selected a recording item. Exceptions
* are when clearing the view.
* @param RecordingModel recording
*/
setCurrentRecording: function (recording) {
@ -397,9 +386,12 @@ let PerformanceController = {
* Get most recently added recording that was triggered manually (via UI).
* @return RecordingModel
*/
getLatestRecording: function () {
getLatestManualRecording: function () {
for (let i = this._recordings.length - 1; i >= 0; i--) {
return this._recordings[i];
let model = this._recordings[i];
if (!model.isConsole() && !model.isImported()) {
return this._recordings[i];
}
}
return null;
},
@ -414,14 +406,6 @@ let PerformanceController = {
return RecordingUtils.getFilteredBlueprint({ blueprint, hiddenMarkers });
},
/**
* Fired whenever the PerformanceFront emits markers, memory or ticks.
*/
_onTimelineData: function (...data) {
this._recordings.forEach(e => e.addTimelineData.apply(e, data));
this.emit(EVENTS.TIMELINE_DATA, ...data);
},
/**
* Fired from RecordingsView, we listen on the PerformanceController so we can
* set it here and re-emit on the controller, where all views can listen.
@ -451,6 +435,37 @@ let PerformanceController = {
this.emit(EVENTS.THEME_CHANGED, data.newValue);
},
/**
* Fired when `console.profile()` is executed.
*/
_onConsoleProfileStart: function (_, recording) {
this._recordings.push(recording);
this.emit(EVENTS.CONSOLE_RECORDING_STARTED, recording);
},
/**
* Fired when `console.profileEnd()` is executed, and the profile
* is stopping soon, as it fetches profiler data.
*/
_onConsoleProfileEnding: function (_, recording) {
this.emit(EVENTS.CONSOLE_RECORDING_WILL_STOP, recording);
},
/**
* Fired when `console.profileEnd()` is executed, and
* has a corresponding `console.profile()` session.
*/
_onConsoleProfileEnd: function (_, recording) {
this.emit(EVENTS.CONSOLE_RECORDING_STOPPED, recording);
},
/**
* Returns the internal store of recording models.
*/
getRecordings: function () {
return this._recordings;
},
toString: () => "[object PerformanceController]"
};

View File

@ -20,6 +20,10 @@ let PerformanceView = {
{ deck: "#performance-view", pane: "#performance-view-content" },
{ deck: "#details-pane-container", pane: "#recording-notice" }
],
"console-recording": [
{ deck: "#performance-view", pane: "#performance-view-content" },
{ deck: "#details-pane-container", pane: "#console-recording-notice" }
],
recorded: [
{ deck: "#performance-view", pane: "#performance-view-content" },
{ deck: "#details-pane-container", pane: "#details-pane" }
@ -55,6 +59,7 @@ let PerformanceView = {
PerformanceController.on(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
PerformanceController.on(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
this.setState("empty");
@ -81,6 +86,7 @@ let PerformanceView = {
PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
PerformanceController.off(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
yield ToolbarView.destroy();
@ -91,7 +97,7 @@ let PerformanceView = {
/**
* Sets the state of the profiler view. Possible options are "empty",
* "recording", "recorded".
* "recording", "console-recording", "recorded".
*/
setState: function (state) {
let viewConfig = this.states[state];
@ -103,6 +109,13 @@ let PerformanceView = {
}
this._state = state;
if (state === "console-recording") {
let recording = PerformanceController.getCurrentRecording();
let label = recording.getLabel() || "";
$(".console-profile-recording-notice").value = L10N.getFormatStr("consoleProfile.recordingNotice", label);
$(".console-profile-stop-notice").value = L10N.getFormatStr("consoleProfile.stopCommand", label);
}
this.emit(EVENTS.UI_STATE_CHANGED, state);
},
@ -148,11 +161,14 @@ let PerformanceView = {
* When a recording is complete.
*/
_onRecordingStopped: function (_, recording) {
this._unlockRecordButton();
// A stopped recording can be from `console.profileEnd` -- only unlock
// the button if it's the main recording that was started via UI.
if (!recording.isConsole()) {
this._unlockRecordButton();
}
// If this recording stopped is the current recording, set the
// state to "recorded". A stopped recording doesn't necessarily
// have to be the current recording (console.profileEnd, for example)
// If the currently selected recording is the one that just stopped,
// switch state to "recorded".
if (recording === PerformanceController.getCurrentRecording()) {
this.setState("recorded");
}
@ -196,6 +212,8 @@ let PerformanceView = {
_onRecordingSelected: function (_, recording) {
if (!recording) {
this.setState("empty");
} else if (recording.isRecording() && recording.isConsole()) {
this.setState("console-recording");
} else if (recording.isRecording()) {
this.setState("recording");
} else {

View File

@ -158,6 +158,16 @@
checked="true" />
<label value="&profilerUI.stopNotice2;"/>
</hbox>
<hbox id="console-recording-notice"
class="notice-container"
align="center"
pack="center"
flex="1">
<vbox>
<label class="console-profile-recording-notice" />
<label class="console-profile-stop-notice" />
</vbox>
</hbox>
<deck id="details-pane" flex="1">
<hbox id="waterfall-view" flex="1">
<vbox id="waterfall-breakdown" flex="1" />

View File

@ -23,6 +23,14 @@ support-files =
[browser_perf-clear-02.js]
[browser_perf-columns-js-calltree.js]
[browser_perf-columns-memory-calltree.js]
[browser_perf-console-record-01.js]
[browser_perf-console-record-02.js]
[browser_perf-console-record-03.js]
[browser_perf-console-record-04.js]
[browser_perf-console-record-05.js]
[browser_perf-console-record-06.js]
[browser_perf-console-record-07.js]
[browser_perf-console-record-08.js]
[browser_perf-data-massaging-01.js]
[browser_perf-data-samples.js]
[browser_perf-details-calltree-render.js]

View File

@ -9,15 +9,17 @@ const TEST_URL = EXAMPLE_URL + "doc_innerHTML.html"
function* getMarkers(front) {
const { promise, resolve } = Promise.defer();
const handler = (_, markers) => {
resolve(markers);
const handler = (_, name, markers) => {
if (name === "markers") {
resolve(markers);
}
};
front.on("markers", handler);
front.on("timeline-data", handler);
yield front.startRecording({ withTicks: true });
const markers = yield promise;
front.off("markers", handler);
front.off("timeline-data", handler);
yield front.stopRecording();
return markers;

View File

@ -18,11 +18,7 @@ function spawnTest () {
ok(memory, "memory should be mocked.");
ok(timeline, "timeline should be mocked.");
let {
profilerStartTime,
timelineStartTime,
memoryStartTime
} = yield front.startRecording({
let recording = yield front.startRecording({
withTicks: true,
withMemory: true,
withAllocations: true,
@ -30,36 +26,22 @@ function spawnTest () {
allocationsMaxLogLength: Services.prefs.getIntPref(MEMORY_MAX_LOG_LEN_PREF)
});
ok(typeof profilerStartTime === "number",
"The front.startRecording() emits a profiler start time.");
ok(typeof timelineStartTime === "number",
"The front.startRecording() emits a timeline start time.");
ok(typeof memoryStartTime === "number",
"The front.startRecording() emits a memory start time.");
ok(typeof recording._profilerStartTime === "number",
"The front.startRecording() returns a recording with a profiler start time");
ok(typeof recording._timelineStartTime === "number",
"The front.startRecording() returns a recording with a timeline start time");
ok(typeof recording._memoryStartTime === "number",
"The front.startRecording() returns a recording with a memory start time");
yield busyWait(WAIT_TIME);
let {
profilerEndTime,
timelineEndTime,
memoryEndTime
} = yield front.stopRecording({
withAllocations: true
});
yield front.stopRecording(recording);
ok(typeof profilerEndTime === "number",
"The front.stopRecording() emits a profiler end time.");
ok(typeof timelineEndTime === "number",
"The front.stopRecording() emits a timeline end time.");
ok(typeof memoryEndTime === "number",
"The front.stopRecording() emits a memory end time.");
ok(typeof recording.getDuration() === "number",
"The front.stopRecording() allows recording to get a duration.");
ok(profilerEndTime > profilerStartTime,
ok(recording.getDuration() >= 0,
"The profilerEndTime is after profilerStartTime.");
is(timelineEndTime, timelineStartTime,
"The timelineEndTime is the same as timelineStartTime.");
is(memoryEndTime, memoryStartTime,
"The memoryEndTime is the same as memoryStartTime.");
yield removeTab(target.tab);
finish();

View File

@ -17,11 +17,7 @@ function spawnTest () {
ok(memory, "memory should be mocked.");
ok(!timeline, "timeline should not be mocked.");
let {
profilerStartTime,
timelineStartTime,
memoryStartTime
} = yield front.startRecording({
let recording = yield front.startRecording({
withTicks: true,
withMemory: true,
withAllocations: true,
@ -29,36 +25,22 @@ function spawnTest () {
allocationsMaxLogLength: Services.prefs.getIntPref(MEMORY_MAX_LOG_LEN_PREF)
});
ok(typeof profilerStartTime === "number",
"The front.startRecording() emits a profiler start time.");
ok(typeof timelineStartTime === "number",
"The front.startRecording() emits a timeline start time.");
ok(typeof memoryStartTime === "number",
"The front.startRecording() emits a memory start time.");
ok(typeof recording._profilerStartTime === "number",
"The front.startRecording() returns a recording with a profiler start time");
ok(typeof recording._timelineStartTime === "number",
"The front.startRecording() returns a recording with a timeline start time");
ok(typeof recording._memoryStartTime === "number",
"The front.startRecording() returns a recording with a memory start time");
yield busyWait(WAIT_TIME);
let {
profilerEndTime,
timelineEndTime,
memoryEndTime
} = yield front.stopRecording({
withAllocations: true
});
yield front.stopRecording(recording);
ok(typeof profilerEndTime === "number",
"The front.stopRecording() emits a profiler end time.");
ok(typeof timelineEndTime === "number",
"The front.stopRecording() emits a timeline end time.");
ok(typeof memoryEndTime === "number",
"The front.stopRecording() emits a memory end time.");
ok(typeof recording.getDuration() === "number",
"The front.stopRecording() allows recording to get a duration.");
ok(profilerEndTime > profilerStartTime,
ok(recording.getDuration() >= 0,
"The profilerEndTime is after profilerStartTime.");
ok(timelineEndTime > timelineStartTime,
"The timelineEndTime is after timelineStartTime.");
is(memoryEndTime, memoryStartTime,
"The memoryEndTime is the same as memoryStartTime.");
yield removeTab(target.tab);
finish();

View File

@ -0,0 +1,44 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the profiler is populated by console recordings that have finished
* before it was opened.
*/
let { getPerformanceActorsConnection } = devtools.require("devtools/performance/front");
let WAIT_TIME = 10;
function spawnTest () {
let profilerConnected = waitForProfilerConnection();
let { target, toolbox, console } = yield initConsole(SIMPLE_URL);
yield profilerConnected;
let connection = getPerformanceActorsConnection(target);
let profileStart = once(connection, "console-profile-start");
console.profile("rust");
yield profileStart;
busyWait(WAIT_TIME);
let profileEnd = once(connection, "console-profile-end");
console.profileEnd("rust");
yield profileEnd;
yield gDevTools.showToolbox(target, "performance");
let panel = toolbox.getCurrentPanel();
let { panelWin: { PerformanceController, RecordingsView }} = panel;
let recordings = PerformanceController.getRecordings();
is(recordings.length, 1, "one recording found in the performance panel.");
is(recordings[0].isConsole(), true, "recording came from console.profile.");
is(recordings[0].getLabel(), "rust", "correct label in the recording model.");
is(RecordingsView.selectedItem.attachment, recordings[0],
"The profile from console should be selected as its the only one in the RecordingsView.");
is(RecordingsView.selectedItem.attachment.getLabel(), "rust",
"The profile label for the first recording is correct.");
yield teardown(panel);
finish();
}

View File

@ -0,0 +1,50 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the profiler is populated by in-progress console recordings
* when it is opened.
*/
let { getPerformanceActorsConnection } = devtools.require("devtools/performance/front");
let WAIT_TIME = 10;
function spawnTest () {
let profilerConnected = waitForProfilerConnection();
let { target, toolbox, console } = yield initConsole(SIMPLE_URL);
yield profilerConnected;
let connection = getPerformanceActorsConnection(target);
let profileStart = once(connection, "console-profile-start");
console.profile("rust");
yield profileStart;
profileStart = once(connection, "console-profile-start");
console.profile("rust2");
yield profileStart;
yield gDevTools.showToolbox(target, "performance");
let panel = toolbox.getCurrentPanel();
let { panelWin: { PerformanceController, RecordingsView }} = panel;
let recordings = PerformanceController.getRecordings();
is(recordings.length, 2, "two recordings found in the performance panel.");
is(recordings[0].isConsole(), true, "recording came from console.profile (1).");
is(recordings[0].getLabel(), "rust", "correct label in the recording model (1).");
is(recordings[0].isRecording(), true, "recording is still recording (1).");
is(recordings[1].isConsole(), true, "recording came from console.profile (2).");
is(recordings[1].getLabel(), "rust2", "correct label in the recording model (2).");
is(recordings[1].isRecording(), true, "recording is still recording (2).");
is(RecordingsView.selectedItem.attachment, recordings[0],
"The first console recording should be selected.");
let profileEnd = once(connection, "console-profile-end");
console.profileEnd("rust");
yield profileEnd;
profileEnd = once(connection, "console-profile-end");
console.profileEnd("rust2");
yield profileEnd;
yield teardown(panel);
finish();
}

View File

@ -0,0 +1,52 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the profiler is populated by in-progress console recordings, and
* also console recordings that have finished before it was opened.
*/
let { getPerformanceActorsConnection } = devtools.require("devtools/performance/front");
let WAIT_TIME = 10;
function spawnTest () {
let profilerConnected = waitForProfilerConnection();
let { target, toolbox, console } = yield initConsole(SIMPLE_URL);
yield profilerConnected;
let connection = getPerformanceActorsConnection(target);
let profileStart = once(connection, "console-profile-start");
console.profile("rust");
yield profileStart;
let profileEnd = once(connection, "console-profile-end");
console.profileEnd("rust");
yield profileEnd;
profileStart = once(connection, "console-profile-start");
console.profile("rust2");
yield profileStart;
yield gDevTools.showToolbox(target, "performance");
let panel = toolbox.getCurrentPanel();
let { panelWin: { PerformanceController, RecordingsView }} = panel;
let recordings = PerformanceController.getRecordings();
is(recordings.length, 2, "two recordings found in the performance panel.");
is(recordings[0].isConsole(), true, "recording came from console.profile (1).");
is(recordings[0].getLabel(), "rust", "correct label in the recording model (1).");
is(recordings[0].isRecording(), false, "recording is still recording (1).");
is(recordings[1].isConsole(), true, "recording came from console.profile (2).");
is(recordings[1].getLabel(), "rust2", "correct label in the recording model (2).");
is(recordings[1].isRecording(), true, "recording is still recording (2).");
is(RecordingsView.selectedItem.attachment, recordings[0],
"The first console recording should be selected.");
profileEnd = once(connection, "console-profile-end");
console.profileEnd("rust2");
yield profileEnd;
yield teardown(panel);
finish();
}

View File

@ -0,0 +1,34 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the profiler can handle creation and stopping of console profiles
* after being opened.
*/
function spawnTest () {
loadFrameScripts();
let { target, toolbox, panel } = yield initPerformance(SIMPLE_URL);
let { $, EVENTS, gFront, PerformanceController, OverviewView, RecordingsView } = panel.panelWin;
yield consoleProfile(panel.panelWin, "rust");
let recordings = PerformanceController.getRecordings();
is(recordings.length, 1, "a recordings found in the performance panel.");
is(recordings[0].isConsole(), true, "recording came from console.profile.");
is(recordings[0].getLabel(), "rust", "correct label in the recording model.");
is(recordings[0].isRecording(), true, "recording is still recording.");
is(RecordingsView.selectedItem.attachment, recordings[0],
"The first console recording should be selected.");
// Ensure overview is still rendering
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
yield consoleProfileEnd(panel.panelWin, "rust");
yield teardown(panel);
finish();
}

View File

@ -0,0 +1,43 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that multiple recordings with the same label (non-overlapping) appear
* in the recording list.
*/
function spawnTest () {
loadFrameScripts();
let { target, toolbox, panel } = yield initPerformance(SIMPLE_URL);
let { $, EVENTS, gFront, PerformanceController, OverviewView, RecordingsView } = panel.panelWin;
yield consoleProfile(panel.panelWin, "rust");
let recordings = PerformanceController.getRecordings();
is(recordings.length, 1, "a recordings found in the performance panel.");
is(recordings[0].isConsole(), true, "recording came from console.profile.");
is(recordings[0].getLabel(), "rust", "correct label in the recording model.");
is(recordings[0].isRecording(), true, "recording is still recording.");
is(RecordingsView.selectedItem.attachment, recordings[0],
"The first console recording should be selected.");
// Ensure overview is still rendering
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
yield consoleProfileEnd(panel.panelWin, "rust");
yield consoleProfile(panel.panelWin, "rust");
recordings = PerformanceController.getRecordings();
is(recordings.length, 2, "a recordings found in the performance panel.");
is(recordings[1].isConsole(), true, "recording came from console.profile.");
is(recordings[1].getLabel(), "rust", "correct label in the recording model.");
is(recordings[1].isRecording(), true, "recording is still recording.");
yield consoleProfileEnd(panel.panelWin, "rust");
yield teardown(panel);
finish();
}

View File

@ -0,0 +1,56 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that console recordings can overlap (not completely nested).
*/
function spawnTest () {
loadFrameScripts();
let { target, toolbox, panel } = yield initPerformance(SIMPLE_URL);
let { $, EVENTS, gFront, PerformanceController, OverviewView, RecordingsView, WaterfallView } = panel.panelWin;
yield consoleProfile(panel.panelWin, "rust");
let recordings = PerformanceController.getRecordings();
is(recordings.length, 1, "a recording found in the performance panel.");
is(RecordingsView.selectedItem.attachment, recordings[0],
"The first console recording should be selected.");
yield consoleProfile(panel.panelWin, "golang");
recordings = PerformanceController.getRecordings();
is(recordings.length, 2, "two recordings found in the performance panel.");
is(RecordingsView.selectedItem.attachment, recordings[0],
"The first console recording should still be selected.");
// Ensure overview is still rendering
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
let detailsRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
yield consoleProfileEnd(panel.panelWin, "rust");
yield detailsRendered;
recordings = PerformanceController.getRecordings();
is(recordings.length, 2, "two recordings found in the performance panel.");
is(RecordingsView.selectedItem.attachment, recordings[0],
"The first console recording should still be selected.");
is(RecordingsView.selectedItem.attachment.isRecording(), false,
"The first console recording should no longer be recording.");
detailsRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
yield consoleProfileEnd(panel.panelWin, "golang");
yield detailsRendered;
recordings = PerformanceController.getRecordings();
is(recordings.length, 2, "two recordings found in the performance panel.");
is(RecordingsView.selectedItem.attachment, recordings[0],
"The first console recording should still be selected.");
is(recordings[1].isRecording(), false,
"The second console recording should no longer be recording.");
yield teardown(panel);
finish();
}

View File

@ -0,0 +1,62 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that a call to console.profileEnd() with no label ends the
* most recent console recording, and console.profileEnd() with a label that does not
* match any pending recordings does nothing.
*/
function spawnTest () {
loadFrameScripts();
let { target, toolbox, panel } = yield initPerformance(SIMPLE_URL);
let { $, EVENTS, gFront, PerformanceController, OverviewView, RecordingsView, WaterfallView } = panel.panelWin;
yield consoleProfile(panel.panelWin);
yield consoleProfile(panel.panelWin, "1");
yield consoleProfile(panel.panelWin, "2");
let recordings = PerformanceController.getRecordings();
is(recordings.length, 3, "3 recordings found");
is(RecordingsView.selectedItem.attachment, recordings[0],
"The first console recording should be selected.");
yield consoleProfileEnd(panel.panelWin);
// First off a label-less profileEnd to make sure no other recordings close
consoleProfileEnd(panel.panelWin, "fxos");
yield idleWait(500);
recordings = PerformanceController.getRecordings();
is(recordings.length, 3, "3 recordings found");
is(recordings[0].getLabel(), "", "Checking label of recording 1");
is(recordings[1].getLabel(), "1", "Checking label of recording 2");
is(recordings[2].getLabel(), "2", "Checking label of recording 3");
is(recordings[0].isRecording(), true,
"The not most recent recording should not stop when calling console.profileEnd with no args.");
is(recordings[1].isRecording(), true,
"The not most recent recording should not stop when calling console.profileEnd with no args.");
is(recordings[2].isRecording(), false,
"Only thw most recent recording should stop when calling console.profileEnd with no args.");
let detailsRendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
yield consoleProfileEnd(panel.panelWin);
yield consoleProfileEnd(panel.panelWin);
is(recordings[0].isRecording(), false,
"All recordings should now be ended. (1)");
is(recordings[1].isRecording(), false,
"All recordings should now be ended. (2)");
is(recordings[2].isRecording(), false,
"All recordings should now be ended. (3)");
yield detailsRendered;
consoleProfileEnd(panel.panelWin);
yield idleWait(500);
ok(true, "Calling additional console.profileEnd() with no argument and no pending recordings does not throw.");
yield teardown(panel);
finish();
}

View File

@ -0,0 +1,89 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the profiler can correctly handle simultaneous console and manual
* recordings (via `console.profile` and clicking the record button).
*/
let C = 1; // is console
let R = 2; // is recording
let S = 4; // is selected
function testRecordings (win, expected) {
let recordings = win.PerformanceController.getRecordings();
let current = win.PerformanceController.getCurrentRecording();
is(recordings.length, expected.length, "expected number of recordings");
recordings.forEach((recording, i) => {
ok(recording.isConsole() == !!(expected[i] & C), `recording ${i+1} has expected console state.`);
ok(recording.isRecording() == !!(expected[i] & R), `recording ${i+1} has expected console state.`);
ok((recording === current) == !!(expected[i] & S), `recording ${i+1} has expected selected state.`);
});
}
function spawnTest () {
loadFrameScripts();
let { target, toolbox, panel } = yield initPerformance(SIMPLE_URL);
let win = panel.panelWin;
let { $, EVENTS, gFront, PerformanceController, OverviewView, RecordingsView, WaterfallView } = win;
info("Starting console.profile()...");
yield consoleProfile(win);
testRecordings(win, [C+S+R]);
info("Starting manual recording...");
yield startRecording(panel);
testRecordings(win, [C+R, R+S]);
info("Starting console.profile(\"3\")...");
yield consoleProfile(win, "3");
testRecordings(win, [C+R, R+S, C+R]);
info("Starting console.profile(\"3\")...");
yield consoleProfile(win, "4");
testRecordings(win, [C+R, R+S, C+R, C+R]);
info("Ending console.profileEnd()...");
yield consoleProfileEnd(win);
testRecordings(win, [C+R, R+S, C+R, C]);
ok(OverviewView.isRendering(), "still rendering overview with manual recorded selected.");
let onSelected = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
info("Select last recording...");
RecordingsView.selectedIndex = 3;
yield onSelected;
testRecordings(win, [C+R, R, C+R, C+S]);
ok(!OverviewView.isRendering(), "stop rendering overview when selected completed recording.");
info("Manually stop manual recording...");
yield stopRecording(panel);
testRecordings(win, [C+R, S, C+R, C]);
ok(!OverviewView.isRendering(), "stop rendering overview when selected completed recording.");
onSelected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
info("Select first recording...");
RecordingsView.selectedIndex = 0;
yield onSelected;
testRecordings(win, [C+R+S, 0, C+R, C]);
yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
ok(OverviewView.isRendering(), "should be rendering overview when selected recording in progress.");
info("Ending console.profileEnd()...");
yield consoleProfileEnd(win);
testRecordings(win, [C+R+S, 0, C, C]);
ok(OverviewView.isRendering(), "should still be rendering overview when selected recording in progress.");
info("Start one more manual recording...");
yield startRecording(panel);
testRecordings(win, [C+R, 0, C, C, R+S]);
ok(OverviewView.isRendering(), "should be rendering overview when selected recording in progress.");
info("Stop manual recording...");
yield stopRecording(panel);
testRecordings(win, [C+R, 0, C, C, S]);
ok(!OverviewView.isRendering(), "stop rendering overview when selected completed recording.");
info("Ending console.profileEnd()...");
yield consoleProfileEnd(win);
testRecordings(win, [C, 0, C, C, S]);
ok(!OverviewView.isRendering(), "stop rendering overview when selected completed recording.");
yield teardown(panel);
finish();
}

View File

@ -14,45 +14,41 @@ function spawnTest () {
// Perform the first recording...
let firstRecordingDataStart = yield front.startRecording();
let firstRecordingStartTime = firstRecordingDataStart.profilerStartTime;
let firstRecording = yield front.startRecording();
let firstRecordingStartTime = firstRecording._profilerStartTime;
info("Started profiling at: " + firstRecordingStartTime);
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
let firstRecordingDataStop = yield front.stopRecording();
let firstRecordingFinishTime = firstRecordingDataStop.profilerEndTime;
yield front.stopRecording(firstRecording);
is(firstRecordingStartTime, 0,
"The profiling start time should be 0 for the first recording.");
ok(firstRecordingFinishTime - firstRecordingStartTime >= WAIT_TIME,
ok(firstRecording.getDuration() >= WAIT_TIME,
"The first recording duration is correct.");
ok(firstRecordingFinishTime >= WAIT_TIME,
"The first recording finish time is correct.");
// Perform the second recording...
let secondRecordingDataStart = yield front.startRecording();
let secondRecordingStartTime = secondRecordingDataStart.profilerStartTime;
let secondRecording = yield front.startRecording();
let secondRecordingStartTime = secondRecording._profilerStartTime;
info("Started profiling at: " + secondRecordingStartTime);
busyWait(WAIT_TIME); // allow the profiler module to sample more cpu activity
let secondRecordingDataStop = yield front.stopRecording();
let secondRecordingFinishTime = secondRecordingDataStop.profilerEndTime;
let secondRecordingProfile = secondRecordingDataStop.profile;
yield front.stopRecording(secondRecording);
let secondRecordingProfile = secondRecording.getProfile();
let secondRecordingSamples = secondRecordingProfile.threads[0].samples;
isnot(secondRecordingStartTime, 0,
isnot(secondRecording._profilerStartTime, 0,
"The profiling start time should not be 0 on the second recording.");
ok(secondRecordingFinishTime - secondRecordingStartTime >= WAIT_TIME,
ok(secondRecording.getDuration() >= WAIT_TIME,
"The second recording duration is correct.");
ok(secondRecordingSamples[0].time < secondRecordingStartTime,
"The second recorded sample times were normalized.");
ok(secondRecordingSamples[0].time > 0,
"The second recorded sample times were normalized correctly.");
ok(!secondRecordingSamples.find(e => e.time + secondRecordingStartTime <= firstRecordingFinishTime),
ok(!secondRecordingSamples.find(e => e.time + secondRecordingStartTime <= firstRecording.getDuration()),
"There should be no samples from the first recording in the second one, " +
"even though the total number of frames did not overflow.");

View File

@ -13,10 +13,11 @@ function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let front = panel.panelWin.gFront;
yield front.startRecording();
let rec = yield front.startRecording();
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
let { profile } = yield front.stopRecording();
yield front.stopRecording(rec);
let profile = rec.getProfile();
let sampleCount = 0;
for (let thread of profile.threads) {

View File

@ -10,58 +10,39 @@ let WAIT_TIME = 1000;
function spawnTest () {
let { target, front } = yield initBackend(SIMPLE_URL);
let {
profilerStartTime,
timelineStartTime,
memoryStartTime
} = yield front.startRecording({
let recording = yield front.startRecording({
withAllocations: true,
allocationsSampleProbability: +Services.prefs.getCharPref(MEMORY_SAMPLE_PROB_PREF),
allocationsMaxLogLength: Services.prefs.getIntPref(MEMORY_MAX_LOG_LEN_PREF)
});
let allocationsCount = 0;
let allocationsCounter = () => allocationsCount++;
let allocationsCounter = (_, type) => type === "allocations" && allocationsCount++;
// Record allocation events to ensure it's called more than once
// so we know it's polling
front.on("allocations", allocationsCounter);
front.on("timeline-data", allocationsCounter);
ok(typeof profilerStartTime === "number",
"The front.startRecording() emits a profiler start time.");
ok(typeof timelineStartTime === "number",
"The front.startRecording() emits a timeline start time.");
ok(typeof memoryStartTime === "number",
"The front.startRecording() emits a memory start time.");
ok(typeof recording._profilerStartTime === "number",
"The front.startRecording() returns a recording model with a profiler start time.");
ok(typeof recording._timelineStartTime === "number",
"The front.startRecording() returns a recording model with a timeline start time.");
ok(typeof recording._memoryStartTime === "number",
"The front.startRecording() returns a recording model with a memory start time.");
yield Promise.all([
busyWait(WAIT_TIME),
waitUntil(() => allocationsCount > 1)
]);
let {
profilerEndTime,
timelineEndTime,
memoryEndTime
} = yield front.stopRecording({
withAllocations: true
});
yield front.stopRecording(recording);
front.off("allocations", allocationsCounter);
front.off("timeline-data", allocationsCounter);
ok(typeof profilerEndTime === "number",
"The front.stopRecording() emits a profiler end time.");
ok(typeof timelineEndTime === "number",
"The front.stopRecording() emits a timeline end time.");
ok(typeof memoryEndTime === "number",
"The front.stopRecording() emits a memory end time.");
ok(profilerEndTime > profilerStartTime,
"The profilerEndTime is after profilerStartTime.");
ok(timelineEndTime > timelineStartTime,
"The timelineEndTime is after timelineStartTime.");
ok(memoryEndTime > memoryStartTime,
"The memoryEndTime is after memoryStartTime.");
ok(typeof recording.getDuration() === "number",
"The front.stopRecording() gives the recording model a stop time and duration.");
ok(recording.getDuration() > 0,
"The front.stopRecording() gives a positive duration amount.");
is((yield front._request("memory", "getState")), "detached",
"Memory actor is detached when stopping recording with allocations.");

View File

@ -10,45 +10,22 @@ let WAIT_TIME = 1000;
function spawnTest () {
let { target, front } = yield initBackend(SIMPLE_URL);
let startData = yield front.startRecording();
let { profilerStartTime, timelineStartTime, memoryStartTime } = startData;
let startModel = yield front.startRecording();
let { profilerStartTime, timelineStartTime, memoryStartTime } = startModel;
ok("profilerStartTime" in startData,
"A `profilerStartTime` property is properly set in the recording data.");
ok("timelineStartTime" in startData,
"A `timelineStartTime` property is properly set in the recording data.");
ok("memoryStartTime" in startData,
"A `memoryStartTime` property is properly set in the recording data.");
ok(profilerStartTime !== undefined,
"A `profilerStartTime` property exists in the recording data.");
ok(timelineStartTime !== undefined,
"A `timelineStartTime` property exists in the recording data.");
is(memoryStartTime, 0,
"A `memoryStartTime` property exists in the recording data, but it's 0.");
ok(startModel._profilerStartTime !== undefined,
"A `_profilerStartTime` property exists in the recording model.");
ok(startModel._timelineStartTime !== undefined,
"A `_timelineStartTime` property exists in the recording model.");
ise(startModel._memoryStartTime, 0,
"A `_memoryStartTime` property exists in the recording model, but it's 0.");
yield busyWait(WAIT_TIME);
let stopData = yield front.stopRecording();
let { profile, profilerEndTime, timelineEndTime, memoryEndTime } = stopData;
let stopModel = yield front.stopRecording(startModel);
ok("profile" in stopData,
"A `profile` property is properly set in the recording data.");
ok("profilerEndTime" in stopData,
"A `profilerEndTime` property is properly set in the recording data.");
ok("timelineEndTime" in stopData,
"A `timelineEndTime` property is properly set in the recording data.");
ok("memoryEndTime" in stopData,
"A `memoryEndTime` property is properly set in the recording data.");
ok(profile,
"A `profile` property exists in the recording data.");
ok(profilerEndTime !== undefined,
"A `profilerEndTime` property exists in the recording data.");
ok(timelineEndTime !== undefined,
"A `timelineEndTime` property exists in the recording data.");
is(memoryEndTime, 0,
"A `memoryEndTime` property exists in the recording data, but it's 0.");
ok(stopModel.getProfile(), "recording model has a profile after stopping.");
ok(stopModel.getDuration(), "recording model has a duration after stopping.");
yield removeTab(target.tab);
finish();

View File

@ -23,13 +23,12 @@ function spawnTest () {
ticks: Promise.defer()
};
front.on("markers", handler);
front.on("memory", handler);
front.on("ticks", handler);
front.on("timeline-data", handler);
yield front.startRecording({ withMemory: true, withTicks: true });
yield Promise.all(Object.keys(deferreds).map(type => deferreds[type].promise));
yield front.stopRecording();
front.off("timeline-data", handler);
is(counters.markers.length, 1, "one marker event fired.");
is(counters.memory.length, 3, "three memory events fired.");
@ -38,18 +37,18 @@ function spawnTest () {
yield removeTab(target.tab);
finish();
function handler (name, ...args) {
function handler (_, name, ...args) {
if (name === "markers") {
if (counters.markers.length >= 1) { return; }
let [markers] = args;
ok(markers[0].start, "received atleast one marker with `start`");
ok(markers[0].end, "received atleast one marker with `end`");
ok(markers[0].name, "received atleast one marker with `name`");
counters.markers.push(markers);
front.off(name, handler);
deferreds[name].resolve();
}
else if (name === "memory") {
if (counters.memory.length >= 3) { return; }
let [delta, measurement] = args;
is(typeof delta, "number", "received `delta` in memory event");
ok(delta > lastMemoryDelta, "received `delta` in memory event");
@ -59,6 +58,7 @@ function spawnTest () {
lastMemoryDelta = delta;
}
else if (name === "ticks") {
if (counters.ticks.length >= 3) { return; }
let [delta, timestamps] = args;
ok(delta > lastTickDelta, "received `delta` in ticks event");
@ -75,7 +75,6 @@ function spawnTest () {
if (name === "markers" && counters[name].length === 1 ||
name === "memory" && counters[name].length === 3 ||
name === "ticks" && counters[name].length === 3) {
front.off(name, handler);
deferreds[name].resolve();
}
};

View File

@ -15,16 +15,16 @@ let test = Task.async(function*() {
"The built-in profiler module should not have been automatically started.");
let activated = front.once("profiler-activated");
yield front.startRecording();
let rec = yield front.startRecording();
yield activated;
yield front.stopRecording();
yield front.stopRecording(rec);
ok(nsIProfilerModule.IsActive(),
"The built-in profiler module should still be active (1).");
let alreadyActive = front.once("profiler-already-active");
yield front.startRecording();
rec = yield front.startRecording();
yield alreadyActive;
yield front.stopRecording();
yield front.stopRecording(rec);
ok(nsIProfilerModule.IsActive(),
"The built-in profiler module should still be active (2).");

View File

@ -19,17 +19,17 @@ let test = Task.async(function*() {
let firstFront = firstPanel.panelWin.gFront;
let firstAlreadyActive = firstFront.once("profiler-already-active");
let { profilerStartTime: firstStartTime } = yield firstFront.startRecording();
let recording = yield firstFront.startRecording();
yield firstAlreadyActive;
ok(firstStartTime > 0, "The profiler was not restarted.");
ok(recording._profilerStartTime > 0, "The profiler was not restarted.");
let { panel: secondPanel } = yield initPerformance(SIMPLE_URL);
let secondFront = secondPanel.panelWin.gFront;
let secondAlreadyActive = secondFront.once("profiler-already-active");
let { profilerStartTime: secondStartTime } = yield secondFront.startRecording();
let secondRecording = yield secondFront.startRecording();
yield secondAlreadyActive;
ok(secondStartTime > 0, "The profiler was not restarted.");
ok(secondRecording._profilerStartTime > 0, "The profiler was not restarted.");
yield teardown(firstPanel);
ok(nsIProfilerModule.IsActive(),

View File

@ -185,8 +185,6 @@ function initBackend(aUrl, targetOps={}) {
// TEST_MOCK_TIMELINE_ACTOR = true
merge(target, targetOps);
yield gDevTools.showToolbox(target, "performance");
let connection = getPerformanceActorsConnection(target);
yield connection.open();
@ -217,6 +215,51 @@ function initPerformance(aUrl, selectedTool="performance", targetOps={}) {
});
}
/**
* Initializes a webconsole panel. Returns a target, panel and toolbox reference.
* Also returns a console property that allows calls to `profile` and `profileEnd`.
*/
function initConsole(aUrl) {
return Task.spawn(function*() {
let { target, toolbox, panel } = yield initPerformance(aUrl, "webconsole");
let { hud } = panel;
return {
target, toolbox, panel, console: {
profile: (s) => consoleExecute(hud, "profile", s),
profileEnd: (s) => consoleExecute(hud, "profileEnd", s)
}
};
});
}
function consoleExecute (console, method, val) {
let { ui, jsterm } = console;
let { promise, resolve } = Promise.defer();
let message = `console.${method}("${val}")`;
ui.on("new-messages", handler);
jsterm.execute(message);
let { console: c } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
function handler (event, messages) {
for (let msg of messages) {
if (msg.response._message === message) {
ui.off("new-messages", handler);
resolve();
return;
}
}
}
return promise;
}
function waitForProfilerConnection() {
let { promise, resolve } = Promise.defer();
Services.obs.addObserver(resolve, "performance-actors-connection-opened", false);
return promise.then(() =>
Services.obs.removeObserver(resolve, "performance-actors-connection-opened"));
}
function* teardown(panel) {
info("Destroying the performance tool.");
@ -239,19 +282,27 @@ function consoleMethod (...args) {
if (!mm) {
throw new Error("`loadFrameScripts()` must be called before using frame scripts.");
}
// Terrible ugly hack -- this gets stringified when it uses the
// message manager, so an undefined arg in `console.profileEnd()`
// turns into a stringified "null", which is terrible. This method is only used
// for test helpers, so swap out the argument if its undefined with an empty string.
// Differences between empty string and undefined are tested on the front itself.
if (args[1] == null) {
args[1] = "";
}
mm.sendAsyncMessage("devtools:test:console", args);
}
function* consoleProfile(connection, label) {
let notified = connection.once("profile");
function* consoleProfile(win, label) {
let profileStart = once(win.PerformanceController, win.EVENTS.CONSOLE_RECORDING_STARTED);
consoleMethod("profile", label);
yield notified;
yield profileStart;
}
function* consoleProfileEnd(connection) {
let notified = connection.once("profileEnd");
consoleMethod("profileEnd");
yield notified;
function* consoleProfileEnd(win, label) {
let ended = once(win.PerformanceController, win.EVENTS.CONSOLE_RECORDING_STOPPED);
consoleMethod("profileEnd", label);
yield ended;
}
function command (button) {

View File

@ -17,6 +17,7 @@ let DetailsSubview = {
this._onPrefChanged = this._onPrefChanged.bind(this);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, this._onOverviewRangeChange);
@ -31,6 +32,7 @@ let DetailsSubview = {
clearNamedTimeout("range-change-debounce");
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, this._onOverviewRangeChange);

View File

@ -59,6 +59,7 @@ let DetailsView = {
yield this.selectDefaultView();
yield this.setAvailableViews();
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.PREF_CHANGED, this.setAvailableViews);
@ -76,6 +77,7 @@ let DetailsView = {
component.initialized && (yield component.view.destroy());
}
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.PREF_CHANGED, this.setAvailableViews);

View File

@ -49,6 +49,9 @@ let OverviewView = {
PerformanceController.on(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_WILL_STOP, this._onRecordingWillStop);
},
/**
@ -72,6 +75,9 @@ let OverviewView = {
PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_WILL_STOP, this._onRecordingWillStop);
}),
/**
@ -279,7 +285,7 @@ let OverviewView = {
_prepareNextTick: function () {
// Check here to see if there's still a _timeoutId, incase
// `stop` was called before the _prepareNextTick call was executed.
if (this._timeoutId) {
if (this.isRendering()) {
this._timeoutId = setTimeout(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
}
},
@ -303,10 +309,13 @@ let OverviewView = {
},
/**
* Called when recording will start.
* Called when recording will start. No recording because it does not
* exist yet, but can just disable from here. This will only trigger for
* manual recordings.
*/
_onRecordingWillStart: Task.async(function* (_, recording) {
yield this._checkSelection(recording);
_onRecordingWillStart: Task.async(function* () {
this._onRecordingStateChange();
yield this._checkSelection();
this.markersOverview.dropSelection();
}),
@ -314,21 +323,29 @@ let OverviewView = {
* Called when recording actually starts.
*/
_onRecordingStarted: function (_, recording) {
this._timeoutId = setTimeout(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
this._onRecordingStateChange();
},
/**
* Called when recording will stop.
*/
_onRecordingWillStop: function(_, recording) {
clearTimeout(this._timeoutId);
this._timeoutId = null;
this._onRecordingStateChange();
},
/**
* Called when recording actually stops.
*/
_onRecordingStopped: Task.async(function* (_, recording) {
this._onRecordingStateChange();
// Check to see if the recording that just stopped is the current recording.
// If it is, render the high-res graphs. For manual recordings, it will also
// be the current recording, but profiles generated by `console.profile` can stop
// while having another profile selected -- in this case, OverviewView should keep
// rendering the current recording.
if (recording !== PerformanceController.getCurrentRecording()) {
return;
}
this.render(FRAMERATE_GRAPH_HIGH_RES_INTERVAL);
yield this._checkSelection(recording);
}),
@ -340,21 +357,57 @@ let OverviewView = {
if (!recording) {
return;
}
// If timeout exists, we have something recording, so
// this will still tick away at rendering. Otherwise, force a render.
if (!this._timeoutId) {
this._onRecordingStateChange();
// If this recording is complete, render the high res graph
if (!recording.isRecording()) {
yield this.render(FRAMERATE_GRAPH_HIGH_RES_INTERVAL);
}
yield this._checkSelection(recording);
this.markersOverview.dropSelection();
}),
/**
* Called when a recording is starting, stopping, or about to start/stop.
* Checks the current recording displayed to determine whether or not
* the polling for rendering the overview graph needs to start or stop.
*/
_onRecordingStateChange: function () {
let currentRecording = PerformanceController.getCurrentRecording();
if (!currentRecording || (this.isRendering() && !currentRecording.isRecording())) {
this._stopPolling();
} else if (currentRecording.isRecording() && !this.isRendering()) {
this._startPolling();
}
},
/**
* Start the polling for rendering the overview graph.
*/
_startPolling: function () {
this._timeoutId = setTimeout(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
},
/**
* Stop the polling for rendering the overview graph.
*/
_stopPolling: function () {
clearTimeout(this._timeoutId);
this._timeoutId = null;
},
/**
* Whether or not the overview view is in a state of polling rendering.
*/
isRendering: function () {
return !!this._timeoutId;
},
/**
* Makes sure the selection is enabled or disabled in all the graphs,
* based on whether a recording currently exists and is not in progress.
*/
_checkSelection: Task.async(function* (recording) {
let selectionEnabled = !recording.isRecording();
let selectionEnabled = recording ? !recording.isRecording() : false;
if (yield this._markersGraphAvailable()) {
this.markersOverview.selectionEnabled = selectionEnabled;

View File

@ -24,6 +24,8 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.on(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.on(EVENTS.RECORDING_IMPORTED, this._onRecordingImported);
PerformanceController.on(EVENTS.RECORDINGS_CLEARED, this._onRecordingsCleared);
this.widget.addEventListener("select", this._onSelect, false);
@ -35,6 +37,8 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
destroy: function() {
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.off(EVENTS.CONSOLE_RECORDING_STOPPED, this._onRecordingStopped);
PerformanceController.off(EVENTS.RECORDING_IMPORTED, this._onRecordingImported);
PerformanceController.off(EVENTS.RECORDINGS_CLEARED, this._onRecordingsCleared);
this.widget.removeEventListener("select", this._onSelect, false);
@ -95,26 +99,18 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
* Model of the recording that was started.
*/
_onRecordingStarted: function (_, recording) {
// Insert a "dummy" recording item, to hint that recording has now started.
let recordingItem;
// If a label is specified (e.g due to a call to `console.profile`),
// then try reusing a pre-existing recording item, if there is one.
// This is symmetrical to how `this.handleRecordingEnded` works.
let profileLabel = recording.getLabel();
if (profileLabel) {
recordingItem = this.getItemForAttachment(e => e.getLabel() == profileLabel);
}
// Otherwise, create a new empty recording item.
if (!recordingItem) {
recordingItem = this.addEmptyRecording(recording);
}
// TODO bug 1144388
// If a label is identical to an existing recording item,
// logically group them here.
// For now, insert a "dummy" recording item, to hint that recording has now started.
let recordingItem = this.addEmptyRecording(recording);
// Mark the corresponding item as being a "record in progress".
recordingItem.isRecording = true;
// If this is a manual recording, immediately select it.
if (!recording.getLabel()) {
// If this is a manual recording, immediately select it, or
// select a console profile if its the only one
if (!recording.isConsole() || this.selectedIndex === -1) {
this.selectedItem = recordingItem;
}
},
@ -126,26 +122,18 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
* The model of the recording that just stopped.
*/
_onRecordingStopped: function (_, recording) {
let recordingItem;
// If a label is specified (e.g due to a call to `console.profileEnd`),
// then try reusing a pre-existing recording item, if there is one.
// This is symmetrical to how `this.handleRecordingStarted` works.
let profileLabel = recording.getLabel();
if (profileLabel) {
recordingItem = this.getItemForAttachment(e => e.getLabel() == profileLabel);
}
// Otherwise, just use the first available recording item.
if (!recordingItem) {
recordingItem = this.getItemForPredicate(e => e.isRecording);
}
let recordingItem = this.getItemForPredicate(e => e.attachment === recording);
// Mark the corresponding item as being a "finished recording".
recordingItem.isRecording = false;
// Render the recording item with finalized information (timing, etc)
this.finalizeRecording(recordingItem);
this.forceSelect(recordingItem);
// Select the recording if it was a manual recording only
if (!recording.isConsole()) {
this.forceSelect(recordingItem);
}
},
/**
@ -195,21 +183,11 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
* The select listener for this container.
*/
_onSelect: Task.async(function*({ detail: recordingItem }) {
// TODO 1120699
// show appropriate empty/recording panels for several scenarios below
if (!recordingItem) {
return;
}
let model = recordingItem.attachment;
// If recording, don't abort completely, as we still want to fire an event
// for selection so we can continue repainting the overview graphs.
if (recordingItem.isRecording) {
this.emit(EVENTS.RECORDING_SELECTED, model);
return;
}
this.emit(EVENTS.RECORDING_SELECTED, model);
}),

View File

@ -36,6 +36,8 @@ waitForExplicitFinish();
let gToolEnabled = Services.prefs.getBoolPref("devtools.shadereditor.enabled");
gDevTools.testing = true;
registerCleanupFunction(() => {
info("finish() was called, cleaning up...");
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);

View File

@ -136,3 +136,8 @@ jit.samples2=#1 sample;#1 samples
# LOCALIZATION NOTE (jit.empty):
# This string is displayed when there are no JIT optimizations to display.
jit.empty=No JIT optimizations recorded for this frame.
# LOCALIZATION NOTE (consoleProfile.recordingNotice/stopCommand):
# These strings are displayed when a recording is in progress, that was started from the console.
consoleProfile.recordingNotice=Currently recording profile "%S".
consoleProfile.stopCommand=Stop profiling by typing \"console.profileEnd(\'%S\')\" into the console.

View File

@ -75,6 +75,10 @@
margin: 0;
}
#performance-view .notice-container vbox {
text-align: center;
}
/* Overview Panel */
.record-button {

View File

@ -140,8 +140,6 @@ LoginManagerPrompter.prototype = {
* _showLoginNotification
*
* Displays a notification doorhanger.
* @param aName
* Name of notification
* @param aTitle
* Object with title and optional resource to display with the title, such as a favicon key
* @param aBody
@ -152,8 +150,7 @@ LoginManagerPrompter.prototype = {
* Object with text to be displayed as clickable, along with a bundle to create an action
*
*/
_showLoginNotification : function (aName, aTitle, aBody, aButtons, aActionText) {
this.log("Adding new " + aName + " notification bar");
_showLoginNotification : function (aTitle, aBody, aButtons, aActionText) {
let notifyWin = this._window.top;
let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
let browser = chromeWin.BrowserApp.getBrowserForWindow(notifyWin);
@ -176,7 +173,7 @@ LoginManagerPrompter.prototype = {
var nativeWindow = this._getNativeWindow();
if (nativeWindow)
nativeWindow.doorhanger.show(aBody, aName, aButtons, tabID, options, "LOGIN");
nativeWindow.doorhanger.show(aBody, "password", aButtons, tabID, options, "LOGIN");
},
@ -232,7 +229,7 @@ LoginManagerPrompter.prototype = {
}
];
this._showLoginNotification("password-save", title, notificationText, buttons, actionText);
this._showLoginNotification(title, notificationText, buttons, actionText);
},
/*
@ -289,7 +286,7 @@ LoginManagerPrompter.prototype = {
}
];
this._showLoginNotification("password-change", title, notificationText, buttons);
this._showLoginNotification(title, notificationText, buttons);
},