mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central to b2g-inbound
This commit is contained in:
commit
66755840fb
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}),
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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]"
|
||||
};
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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" />
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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.");
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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.");
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
@ -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).");
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}),
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -75,6 +75,10 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#performance-view .notice-container vbox {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Overview Panel */
|
||||
|
||||
.record-button {
|
||||
|
@ -7139,22 +7139,27 @@ nsContentUtils::GetInnerWindowID(nsIRequest* aRequest)
|
||||
}
|
||||
|
||||
void
|
||||
nsContentUtils::GetHostOrIPv6WithBrackets(nsIURI* aURI, nsAString& aHost)
|
||||
nsContentUtils::GetHostOrIPv6WithBrackets(nsIURI* aURI, nsCString& aHost)
|
||||
{
|
||||
aHost.Truncate();
|
||||
nsAutoCString hostname;
|
||||
nsresult rv = aURI->GetHost(hostname);
|
||||
nsresult rv = aURI->GetHost(aHost);
|
||||
if (NS_FAILED(rv)) { // Some URIs do not have a host
|
||||
return;
|
||||
}
|
||||
|
||||
if (hostname.FindChar(':') != -1) { // Escape IPv6 address
|
||||
MOZ_ASSERT(!hostname.Length() ||
|
||||
(hostname[0] !='[' && hostname[hostname.Length() - 1] != ']'));
|
||||
hostname.Insert('[', 0);
|
||||
hostname.Append(']');
|
||||
if (aHost.FindChar(':') != -1) { // Escape IPv6 address
|
||||
MOZ_ASSERT(!aHost.Length() ||
|
||||
(aHost[0] !='[' && aHost[aHost.Length() - 1] != ']'));
|
||||
aHost.Insert('[', 0);
|
||||
aHost.Append(']');
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsContentUtils::GetHostOrIPv6WithBrackets(nsIURI* aURI, nsAString& aHost)
|
||||
{
|
||||
nsAutoCString hostname;
|
||||
GetHostOrIPv6WithBrackets(aURI, hostname);
|
||||
CopyUTF8toUTF16(hostname, aHost);
|
||||
}
|
||||
|
||||
|
@ -2282,6 +2282,7 @@ public:
|
||||
* otherwise it just outputs the hostname in aHost.
|
||||
*/
|
||||
static void GetHostOrIPv6WithBrackets(nsIURI* aURI, nsAString& aHost);
|
||||
static void GetHostOrIPv6WithBrackets(nsIURI* aURI, nsCString& aHost);
|
||||
|
||||
/*
|
||||
* Call the given callback on all remote children of the given top-level
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
@ -117,7 +116,7 @@
|
||||
{ url: 'http://example.com/carrot#question%3f',
|
||||
base: undefined,
|
||||
error: false,
|
||||
hash: '#question%3f'
|
||||
hash: '#question?'
|
||||
},
|
||||
{ url: 'https://example.com:4443?',
|
||||
base: undefined,
|
||||
|
@ -1897,6 +1897,8 @@ addExternalIface('nsIDOMCrypto', nativeType='nsIDOMCrypto',
|
||||
addExternalIface('nsIInputStreamCallback', nativeType='nsIInputStreamCallback',
|
||||
headerFile='nsIAsyncInputStream.h')
|
||||
addExternalIface('nsIFile', nativeType='nsIFile', notflattened=True)
|
||||
addExternalIface('nsILoadGroup', nativeType='nsILoadGroup',
|
||||
headerFile='nsILoadGroup.h', notflattened=True)
|
||||
addExternalIface('nsIMessageBroadcaster', nativeType='nsIMessageBroadcaster',
|
||||
headerFile='nsIMessageManager.h', notflattened=True)
|
||||
addExternalIface('nsISelectionListener', nativeType='nsISelectionListener')
|
||||
|
@ -8,6 +8,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=422132
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
@ -25,67 +26,96 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=422132
|
||||
/** Test for Bug 422132 **/
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestFlakyTimeout("untriaged");
|
||||
SimpleTest.waitForFocus(function() {
|
||||
SpecialPowers.pushPrefEnv({
|
||||
"set":[["general.smoothScroll", false],
|
||||
["mousewheel.min_line_scroll_amount", 1],
|
||||
["mousewheel.system_scroll_override_on_root_content.enabled", false],
|
||||
["mousewheel.transaction.timeout", 100000]]}, runTests)}, window);
|
||||
|
||||
function hitEventLoop(aFunc, aTimes)
|
||||
{
|
||||
if (--aTimes) {
|
||||
setTimeout(hitEventLoop, 0, aFunc, aTimes);
|
||||
} else {
|
||||
setTimeout(aFunc, 20);
|
||||
}
|
||||
}
|
||||
|
||||
function runTests()
|
||||
{
|
||||
var target = document.getElementById("target");
|
||||
|
||||
var scrollLeft = target.scrollLeft;
|
||||
var scrollTop = target.scrollTop;
|
||||
synthesizeWheel(target, 10, 10,
|
||||
{ deltaMode: WheelEvent.DOM_DELTA_PIXEL,
|
||||
deltaX: 0.5, deltaY: 0.5, lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 });
|
||||
hitEventLoop(function () {
|
||||
is(target.scrollLeft, scrollLeft, "scrolled to right by 0.5px delta value");
|
||||
is(target.scrollTop, scrollTop, "scrolled to bottom by 0.5px delta value");
|
||||
scrollLeft = target.scrollLeft;
|
||||
scrollTop = target.scrollTop;
|
||||
synthesizeWheel(target, 10, 10,
|
||||
{ deltaMode: WheelEvent.DOM_DELTA_PIXEL,
|
||||
deltaX: 0.5, deltaY: 0.5, lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 });
|
||||
hitEventLoop(function () {
|
||||
ok(target.scrollLeft > scrollLeft,
|
||||
"not scrolled to right by 0.5px delta value with pending 0.5px delta");
|
||||
ok(target.scrollTop > scrollTop,
|
||||
"not scrolled to bottom by 0.5px delta value with pending 0.5px delta");
|
||||
scrollLeft = target.scrollLeft;
|
||||
scrollTop = target.scrollTop;
|
||||
synthesizeWheel(target, 10, 10,
|
||||
{ deltaMode: WheelEvent.DOM_DELTA_LINE,
|
||||
deltaX: 0.5, deltaY: 0.5, lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 });
|
||||
hitEventLoop(function () {
|
||||
is(target.scrollLeft, scrollLeft, "scrolled to right by 0.5 line delta value");
|
||||
is(target.scrollTop, scrollTop, "scrolled to bottom by 0.5 line delta value");
|
||||
|
||||
var tests = [
|
||||
{
|
||||
prepare: function() {
|
||||
scrollLeft = target.scrollLeft;
|
||||
scrollTop = target.scrollTop;
|
||||
synthesizeWheel(target, 10, 10,
|
||||
{ deltaMode: WheelEvent.DOM_DELTA_LINE,
|
||||
deltaX: 0.5, deltaY: 0.5, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 });
|
||||
hitEventLoop(function () {
|
||||
ok(target.scrollLeft > scrollLeft,
|
||||
"not scrolled to right by 0.5 line delta value with pending 0.5 line delta");
|
||||
ok(target.scrollTop > scrollTop,
|
||||
"not scrolled to bottom by 0.5 line delta value with pending 0.5 line delta");
|
||||
SimpleTest.finish();
|
||||
}, 20);
|
||||
}, 20);
|
||||
}, 20);
|
||||
}, 20);
|
||||
},
|
||||
event: {
|
||||
deltaMode: WheelEvent.DOM_DELTA_PIXEL,
|
||||
deltaX: 0.5,
|
||||
deltaY: 0.5,
|
||||
lineOrPageDeltaX: 0,
|
||||
lineOrPageDeltaY: 0
|
||||
},
|
||||
}, {
|
||||
event: {
|
||||
deltaMode: WheelEvent.DOM_DELTA_PIXEL,
|
||||
deltaX: 0.5,
|
||||
deltaY: 0.5,
|
||||
lineOrPageDeltaX: 0,
|
||||
lineOrPageDeltaY: 0
|
||||
},
|
||||
check: function() {
|
||||
is(target.scrollLeft - scrollLeft, 1,
|
||||
"not scrolled to right by 0.5px delta value with pending 0.5px delta");
|
||||
is(target.scrollTop - scrollTop, 1,
|
||||
"not scrolled to bottom by 0.5px delta value with pending 0.5px delta");
|
||||
},
|
||||
}, {
|
||||
prepare: function() {
|
||||
scrollLeft = target.scrollLeft;
|
||||
scrollTop = target.scrollTop;
|
||||
},
|
||||
event: {
|
||||
deltaMode: WheelEvent.DOM_DELTA_LINE,
|
||||
deltaX: 0.5,
|
||||
deltaY: 0.5,
|
||||
lineOrPageDeltaX: 0,
|
||||
lineOrPageDeltaY: 0
|
||||
},
|
||||
}, {
|
||||
event: {
|
||||
deltaMode: WheelEvent.DOM_DELTA_LINE,
|
||||
deltaX: 0.5,
|
||||
deltaY: 0.5,
|
||||
lineOrPageDeltaX: 1,
|
||||
lineOrPageDeltaY: 1
|
||||
},
|
||||
check: function() {
|
||||
is(target.scrollLeft - scrollLeft, 1,
|
||||
"not scrolled to right by 0.5 line delta value with pending 0.5 line delta");
|
||||
is(target.scrollTop - scrollTop, 1,
|
||||
"not scrolled to bottom by 0.5 line delta value with pending 0.5 line delta");
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
var nextTest = function() {
|
||||
var test = tests.shift();
|
||||
if (test.prepare) {
|
||||
test.prepare();
|
||||
}
|
||||
|
||||
sendWheelAndPaint(target, 10, 10, test.event, function() {
|
||||
if (test.check) {
|
||||
test.check();
|
||||
}
|
||||
if (tests.length == 0) {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(nextTest, 0);
|
||||
});
|
||||
}
|
||||
|
||||
nextTest();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
@ -7,14 +7,42 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<a id="target1" href="http://www.example.com/#q=♥â¥#hello"></a>
|
||||
<a id="target2" href="http://www.example.com/#q=%E2%99%A5%C3%A2%C2%A5"></a>
|
||||
<a id="target3" href="http://www.example.com/#/search/%23important"></a>
|
||||
<a id="target4" href='http://www.example.com/#{"a":[13, 42], "b":{"key":"value"}}'></a>
|
||||
|
||||
<pre id="test">
|
||||
|
||||
<script>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [['dom.url.encode_decode_hash', false]]}, runTest);
|
||||
|
||||
function runTest() {
|
||||
setupTest();
|
||||
doTestEncoded();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function setupTest() {
|
||||
var target1 = document.createElement("a");
|
||||
target1.id = "target1";
|
||||
target1.href = "http://www.example.com/#q=♥â¥#hello";
|
||||
document.body.appendChild(target1);
|
||||
|
||||
var target2 = document.createElement("a");
|
||||
target2.id = "target2";
|
||||
target2.href = "http://www.example.com/#q=%E2%99%A5%C3%A2%C2%A5";
|
||||
document.body.appendChild(target2);
|
||||
|
||||
var target3 = document.createElement("a");
|
||||
target3.id = "target3";
|
||||
target3.href = "http://www.example.com/#/search/%23important";
|
||||
document.body.appendChild(target3);
|
||||
|
||||
var target4 = document.createElement("a");
|
||||
target4.id = "target4";
|
||||
target4.href = 'http://www.example.com/#{"a":[13, 42], "b":{"key":"value"}}';
|
||||
document.body.appendChild(target4);
|
||||
}
|
||||
|
||||
function doTestEncoded() {
|
||||
// Tests Link::GetHash
|
||||
|
||||
// Check that characters aren't being encoded
|
||||
@ -32,9 +60,7 @@
|
||||
// Some JSON
|
||||
target = document.getElementById("target4");
|
||||
is(target.hash, '#{"a":[13, 42], "b":{"key":"value"}}', 'Unexpected link hash');
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Tests URL::GetHash
|
||||
|
||||
var url = new URL("http://www.example.com/#q=♥â¥#hello")
|
||||
@ -68,9 +94,7 @@
|
||||
is(target.hash, '#{"a":[13, 42], "b":{"key":"value"}}', 'Unexpected url hash');
|
||||
parsed = JSON.parse(target.hash.substring(1));
|
||||
is(parsed.b.key, 'value', 'JSON not parsed correctly');
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Tests nsLocation::GetHash
|
||||
|
||||
window.history.pushState(1, document.title, '#q=♥â¥#hello');
|
||||
@ -84,6 +108,9 @@
|
||||
|
||||
window.history.pushState(1, document.title, '#{"a":[13, 42], "b":{"key":"value"}}');
|
||||
is(location.hash,'#{"a":[13, 42], "b":{"key":"value"}}', 'Unexpected location hash');
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</pre>
|
||||
|
@ -40,16 +40,19 @@ function ResourceLoader(res, rej) {
|
||||
this.data = '';
|
||||
}
|
||||
|
||||
/** Loads the identified https:// URL. */
|
||||
ResourceLoader.load = function(uri) {
|
||||
/** Loads the identified https:// URL. */
|
||||
ResourceLoader.load = function(uri, doc) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let listener = new ResourceLoader(resolve, reject);
|
||||
let ioService = Cc['@mozilla.org/network/io-service;1']
|
||||
.getService(Ci.nsIIOService);
|
||||
let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
|
||||
// the '2' identifies this as a script load
|
||||
let ioChannel = ioService.newChannelFromURI2(uri, null, systemPrincipal,
|
||||
systemPrincipal, 0, 2);
|
||||
let ioChannel = ioService.newChannelFromURI2(uri, doc, doc.nodePrincipal,
|
||||
systemPrincipal, 0,
|
||||
Ci.nsIContentPolicy.TYPE_SCRIPT);
|
||||
|
||||
ioChannel.loadGroup = doc.documentLoadGroup.QueryInterface(Ci.nsILoadGroup);
|
||||
ioChannel.notificationCallbacks = new RedirectHttpsOnly();
|
||||
ioChannel.asyncOpen(listener, null);
|
||||
});
|
||||
@ -110,12 +113,14 @@ function createLocationFromURI(uri) {
|
||||
*
|
||||
* @param domain (string) the domain of the IdP
|
||||
* @param protocol (string?) the protocol of the IdP [default: 'default']
|
||||
* @param doc (obj) the current document
|
||||
* @throws if the domain or protocol aren't valid
|
||||
*/
|
||||
function IdpSandbox(domain, protocol) {
|
||||
function IdpSandbox(domain, protocol, doc) {
|
||||
this.source = IdpSandbox.createIdpUri(domain, protocol || "default");
|
||||
this.active = null;
|
||||
this.sandbox = null;
|
||||
this.document = doc;
|
||||
}
|
||||
|
||||
IdpSandbox.checkDomain = function(domain) {
|
||||
@ -176,7 +181,7 @@ IdpSandbox.prototype = {
|
||||
|
||||
start: function() {
|
||||
if (!this.active) {
|
||||
this.active = ResourceLoader.load(this.source)
|
||||
this.active = ResourceLoader.load(this.source, this.document)
|
||||
.then(result => this._createSandbox(result));
|
||||
}
|
||||
return this.active;
|
||||
|
@ -1223,9 +1223,10 @@ void MediaDecoder::UpdateReadyStateForData()
|
||||
mOwner->UpdateReadyStateForData(frameStatus);
|
||||
}
|
||||
|
||||
void MediaDecoder::OnSeekResolvedInternal(bool aAtEnd, MediaDecoderEventVisibility aEventVisibility)
|
||||
void MediaDecoder::OnSeekResolved(SeekResolveValue aVal)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mSeekRequest.Complete();
|
||||
|
||||
if (mShuttingDown)
|
||||
return;
|
||||
@ -1242,20 +1243,20 @@ void MediaDecoder::OnSeekResolvedInternal(bool aAtEnd, MediaDecoderEventVisibili
|
||||
seekWasAborted = true;
|
||||
} else {
|
||||
UnpinForSeek();
|
||||
fireEnded = aAtEnd;
|
||||
if (aAtEnd) {
|
||||
fireEnded = aVal.mAtEnd;
|
||||
if (aVal.mAtEnd) {
|
||||
ChangeState(PLAY_STATE_ENDED);
|
||||
} else if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
|
||||
ChangeState(aAtEnd ? PLAY_STATE_ENDED : mNextState);
|
||||
} else if (aVal.mEventVisibility != MediaDecoderEventVisibility::Suppressed) {
|
||||
ChangeState(aVal.mAtEnd ? PLAY_STATE_ENDED : mNextState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlaybackPositionChanged(aEventVisibility);
|
||||
PlaybackPositionChanged(aVal.mEventVisibility);
|
||||
|
||||
if (mOwner) {
|
||||
UpdateReadyStateForData();
|
||||
if (!seekWasAborted && (aEventVisibility != MediaDecoderEventVisibility::Suppressed)) {
|
||||
if (!seekWasAborted && (aVal.mEventVisibility != MediaDecoderEventVisibility::Suppressed)) {
|
||||
mOwner->SeekCompleted();
|
||||
if (fireEnded) {
|
||||
mOwner->PlaybackEnded();
|
||||
|
@ -809,21 +809,7 @@ public:
|
||||
void PlaybackEnded();
|
||||
|
||||
void OnSeekRejected() { mSeekRequest.Complete(); }
|
||||
void OnSeekResolvedInternal(bool aAtEnd, MediaDecoderEventVisibility aEventVisibility);
|
||||
|
||||
void OnSeekResolved(SeekResolveValue aVal)
|
||||
{
|
||||
mSeekRequest.Complete();
|
||||
OnSeekResolvedInternal(aVal.mAtEnd, aVal.mEventVisibility);
|
||||
}
|
||||
|
||||
#ifdef MOZ_AUDIO_OFFLOAD
|
||||
// Temporary hack - see bug 1139206.
|
||||
void SimulateSeekResolvedForAudioOffload(MediaDecoderEventVisibility aEventVisibility)
|
||||
{
|
||||
OnSeekResolvedInternal(false, aEventVisibility);
|
||||
}
|
||||
#endif
|
||||
void OnSeekResolved(SeekResolveValue aVal);
|
||||
|
||||
// Seeking has started. Inform the element on the main
|
||||
// thread.
|
||||
|
@ -56,7 +56,7 @@ PeerConnectionIdp.prototype = {
|
||||
}
|
||||
this._idp.stop();
|
||||
}
|
||||
this._idp = new IdpSandbox(provider, protocol);
|
||||
this._idp = new IdpSandbox(provider, protocol, this._win.document);
|
||||
},
|
||||
|
||||
// start the IdP and do some error fixup
|
||||
|
@ -57,13 +57,10 @@ static const uint64_t OFFLOAD_PAUSE_MAX_MSECS = 60000ll;
|
||||
AudioOffloadPlayer::AudioOffloadPlayer(MediaOmxCommonDecoder* aObserver) :
|
||||
mStarted(false),
|
||||
mPlaying(false),
|
||||
mSeeking(false),
|
||||
mReachedEOS(false),
|
||||
mSeekDuringPause(false),
|
||||
mIsElementVisible(true),
|
||||
mSampleRate(0),
|
||||
mStartPosUs(0),
|
||||
mSeekTimeUs(0),
|
||||
mPositionTimeMediaUs(-1),
|
||||
mInputBuffer(nullptr),
|
||||
mObserver(aObserver)
|
||||
@ -199,13 +196,6 @@ status_t AudioOffloadPlayer::ChangeState(MediaDecoder::PlayState aState)
|
||||
StartTimeUpdate();
|
||||
} break;
|
||||
|
||||
case MediaDecoder::PLAY_STATE_SEEKING: {
|
||||
int64_t seekTimeUs
|
||||
= mObserver->GetSeekTime();
|
||||
SeekTo(seekTimeUs, true);
|
||||
mObserver->ResetSeekTime();
|
||||
} break;
|
||||
|
||||
case MediaDecoder::PLAY_STATE_PAUSED:
|
||||
case MediaDecoder::PLAY_STATE_SHUTDOWN:
|
||||
// Just pause here during play state shutdown as well to stop playing
|
||||
@ -278,8 +268,12 @@ status_t AudioOffloadPlayer::Play()
|
||||
return err;
|
||||
}
|
||||
// Seek to last play position only when there was no seek during last pause
|
||||
if (!mSeeking) {
|
||||
SeekTo(mPositionTimeMediaUs);
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
if (!mSeekTarget.IsValid()) {
|
||||
mSeekTarget = SeekTarget(mPositionTimeMediaUs,
|
||||
SeekTarget::Accurate,
|
||||
MediaDecoderEventVisibility::Suppressed);
|
||||
DoSeek();
|
||||
}
|
||||
}
|
||||
|
||||
@ -343,28 +337,36 @@ void AudioOffloadPlayer::Reset()
|
||||
WakeLockRelease();
|
||||
}
|
||||
|
||||
status_t AudioOffloadPlayer::SeekTo(int64_t aTimeUs, bool aDispatchSeekEvents)
|
||||
nsRefPtr<MediaDecoder::SeekPromise> AudioOffloadPlayer::Seek(SeekTarget aTarget)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
CHECK(mAudioSink.get());
|
||||
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
|
||||
AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("SeekTo ( %lld )", aTimeUs));
|
||||
mSeekPromise.RejectIfExists(true, __func__);
|
||||
mSeekTarget = aTarget;
|
||||
nsRefPtr<MediaDecoder::SeekPromise> p = mSeekPromise.Ensure(__func__);
|
||||
DoSeek();
|
||||
return p;
|
||||
}
|
||||
|
||||
status_t AudioOffloadPlayer::DoSeek()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mSeekTarget.IsValid());
|
||||
CHECK(mAudioSink.get());
|
||||
|
||||
AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("DoSeek ( %lld )", mSeekTarget.mTime));
|
||||
|
||||
mSeeking = true;
|
||||
mReachedEOS = false;
|
||||
mPositionTimeMediaUs = -1;
|
||||
mSeekTimeUs = aTimeUs;
|
||||
mStartPosUs = aTimeUs;
|
||||
mDispatchSeekEvents = aDispatchSeekEvents;
|
||||
mStartPosUs = mSeekTarget.mTime;
|
||||
|
||||
if (mDispatchSeekEvents) {
|
||||
if (!mSeekPromise.IsEmpty()) {
|
||||
nsCOMPtr<nsIRunnable> nsEvent =
|
||||
NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
|
||||
mObserver,
|
||||
&MediaDecoder::SeekingStarted,
|
||||
MediaDecoderEventVisibility::Observable);
|
||||
mSeekTarget.mEventVisibility);
|
||||
NS_DispatchToCurrentThread(nsEvent);
|
||||
}
|
||||
|
||||
@ -374,21 +376,15 @@ status_t AudioOffloadPlayer::SeekTo(int64_t aTimeUs, bool aDispatchSeekEvents)
|
||||
mAudioSink->Start();
|
||||
|
||||
} else {
|
||||
mSeekDuringPause = true;
|
||||
|
||||
if (mStarted) {
|
||||
mAudioSink->Flush();
|
||||
}
|
||||
|
||||
if (mDispatchSeekEvents) {
|
||||
mDispatchSeekEvents = false;
|
||||
if (!mSeekPromise.IsEmpty()) {
|
||||
AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Fake seek complete during pause"));
|
||||
nsCOMPtr<nsIRunnable> nsEvent =
|
||||
NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
|
||||
mObserver,
|
||||
&MediaDecoder::SimulateSeekResolvedForAudioOffload,
|
||||
MediaDecoderEventVisibility::Observable);
|
||||
NS_DispatchToCurrentThread(nsEvent);
|
||||
// We do not reset mSeekTarget here.
|
||||
MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
|
||||
mSeekPromise.Resolve(val, __func__);
|
||||
}
|
||||
}
|
||||
|
||||
@ -407,8 +403,8 @@ int64_t AudioOffloadPlayer::GetMediaTimeUs()
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
|
||||
int64_t playPosition = 0;
|
||||
if (mSeeking) {
|
||||
return mSeekTimeUs;
|
||||
if (mSeekTarget.IsValid()) {
|
||||
return mSeekTarget.mTime;
|
||||
}
|
||||
if (!mStarted) {
|
||||
return mPositionTimeMediaUs;
|
||||
@ -439,6 +435,12 @@ int64_t AudioOffloadPlayer::GetOutputPlayPositionUs_l() const
|
||||
|
||||
void AudioOffloadPlayer::NotifyAudioEOS()
|
||||
{
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
// We do not reset mSeekTarget here.
|
||||
if (!mSeekPromise.IsEmpty()) {
|
||||
MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
|
||||
mSeekPromise.Resolve(val, __func__);
|
||||
}
|
||||
nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
|
||||
&MediaDecoder::PlaybackEnded);
|
||||
NS_DispatchToMainThread(nsEvent);
|
||||
@ -456,6 +458,15 @@ void AudioOffloadPlayer::NotifyPositionChanged()
|
||||
|
||||
void AudioOffloadPlayer::NotifyAudioTearDown()
|
||||
{
|
||||
// Fallback to state machine.
|
||||
// state machine's seeks will be done with
|
||||
// MediaDecoderEventVisibility::Suppressed.
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
// We do not reset mSeekTarget here.
|
||||
if (!mSeekPromise.IsEmpty()) {
|
||||
MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
|
||||
mSeekPromise.Resolve(val, __func__);
|
||||
}
|
||||
nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver,
|
||||
&MediaOmxCommonDecoder::AudioOffloadTearDown);
|
||||
NS_DispatchToMainThread(nsEvent);
|
||||
@ -506,27 +517,26 @@ size_t AudioOffloadPlayer::FillBuffer(void* aData, size_t aSize)
|
||||
|
||||
size_t sizeDone = 0;
|
||||
size_t sizeRemaining = aSize;
|
||||
int64_t seekTimeUs = -1;
|
||||
while (sizeRemaining > 0) {
|
||||
MediaSource::ReadOptions options;
|
||||
bool refreshSeekTime = false;
|
||||
|
||||
{
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
|
||||
if (mSeeking) {
|
||||
options.setSeekTo(mSeekTimeUs);
|
||||
if (mSeekTarget.IsValid()) {
|
||||
seekTimeUs = mSeekTarget.mTime;
|
||||
options.setSeekTo(seekTimeUs);
|
||||
refreshSeekTime = true;
|
||||
|
||||
if (mInputBuffer) {
|
||||
mInputBuffer->release();
|
||||
mInputBuffer = nullptr;
|
||||
}
|
||||
mSeeking = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mInputBuffer) {
|
||||
|
||||
status_t err;
|
||||
err = mSource->read(&mInputBuffer, &options);
|
||||
|
||||
@ -535,6 +545,9 @@ size_t AudioOffloadPlayer::FillBuffer(void* aData, size_t aSize)
|
||||
android::Mutex::Autolock autoLock(mLock);
|
||||
|
||||
if (err != OK) {
|
||||
if (mSeekTarget.IsValid()) {
|
||||
mSeekTarget.Reset();
|
||||
}
|
||||
AUDIO_OFFLOAD_LOG(PR_LOG_ERROR, ("Error while reading media source %d "
|
||||
"Ok to receive EOS error at end", err));
|
||||
if (!mReachedEOS) {
|
||||
@ -564,25 +577,19 @@ size_t AudioOffloadPlayer::FillBuffer(void* aData, size_t aSize)
|
||||
kKeyTime, &mPositionTimeMediaUs));
|
||||
}
|
||||
|
||||
if (refreshSeekTime) {
|
||||
if (mDispatchSeekEvents && !mSeekDuringPause) {
|
||||
mDispatchSeekEvents = false;
|
||||
if (mSeekTarget.IsValid() && seekTimeUs == mSeekTarget.mTime) {
|
||||
MOZ_ASSERT(mSeekTarget.IsValid());
|
||||
mSeekTarget.Reset();
|
||||
if (!mSeekPromise.IsEmpty()) {
|
||||
AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("FillBuffer posting SEEK_COMPLETE"));
|
||||
nsCOMPtr<nsIRunnable> nsEvent =
|
||||
NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
|
||||
mObserver,
|
||||
&MediaDecoder::SimulateSeekResolvedForAudioOffload,
|
||||
MediaDecoderEventVisibility::Observable);
|
||||
NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL);
|
||||
|
||||
} else if (mSeekDuringPause) {
|
||||
// Callback is already called for seek during pause. Just reset the
|
||||
// flag
|
||||
AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Not posting seek complete as its"
|
||||
" already faked"));
|
||||
mSeekDuringPause = false;
|
||||
MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
|
||||
mSeekPromise.Resolve(val, __func__);
|
||||
}
|
||||
} else if (mSeekTarget.IsValid()) {
|
||||
AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("seek is updated during unlocking mLock"));
|
||||
}
|
||||
|
||||
if (refreshSeekTime) {
|
||||
NotifyPositionChanged();
|
||||
|
||||
// need to adjust the mStartPosUs for offload decoding since parser
|
||||
@ -590,14 +597,6 @@ size_t AudioOffloadPlayer::FillBuffer(void* aData, size_t aSize)
|
||||
mStartPosUs = mPositionTimeMediaUs;
|
||||
AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Adjust seek time to: %.2f",
|
||||
mStartPosUs / 1E6));
|
||||
|
||||
// clear seek time with mLock locked and once we have valid
|
||||
// mPositionTimeMediaUs
|
||||
// before clearing mSeekTimeUs check if a new seek request has been
|
||||
// received while we were reading from the source with mLock released.
|
||||
if (!mSeeking) {
|
||||
mSeekTimeUs = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,25 +78,27 @@ public:
|
||||
~AudioOffloadPlayer();
|
||||
|
||||
// Caller retains ownership of "aSource".
|
||||
void SetSource(const android::sp<MediaSource> &aSource);
|
||||
virtual void SetSource(const android::sp<MediaSource> &aSource) override;
|
||||
|
||||
// Start the source if it's not already started and open the AudioSink to
|
||||
// create an offloaded audio track
|
||||
status_t Start(bool aSourceAlreadyStarted = false);
|
||||
virtual status_t Start(bool aSourceAlreadyStarted = false) override;
|
||||
|
||||
double GetMediaTimeSecs();
|
||||
virtual status_t ChangeState(MediaDecoder::PlayState aState) override;
|
||||
|
||||
virtual void SetVolume(double aVolume) override;
|
||||
|
||||
virtual double GetMediaTimeSecs() override;
|
||||
|
||||
// To update progress bar when the element is visible
|
||||
void SetElementVisibility(bool aIsVisible);
|
||||
|
||||
status_t ChangeState(MediaDecoder::PlayState aState);
|
||||
|
||||
void SetVolume(double aVolume);
|
||||
virtual void SetElementVisibility(bool aIsVisible) override;;
|
||||
|
||||
// Update ready state based on current play state. Not checking data
|
||||
// availability since offloading is currently done only when whole compressed
|
||||
// data is available
|
||||
MediaDecoderOwner::NextFrameStatus GetNextFrameStatus();
|
||||
virtual MediaDecoderOwner::NextFrameStatus GetNextFrameStatus() override;
|
||||
|
||||
virtual nsRefPtr<MediaDecoder::SeekPromise> Seek(SeekTarget aTarget) override;
|
||||
|
||||
void TimeUpdate();
|
||||
|
||||
@ -112,28 +114,12 @@ private:
|
||||
// Used only in main thread
|
||||
bool mPlaying;
|
||||
|
||||
// Set when playstate is seeking and reset when FillBUffer() acknowledged
|
||||
// seeking by seeking audio source. Used in main thread and offload
|
||||
// callback thread, protected by Mutex mLock
|
||||
bool mSeeking;
|
||||
|
||||
// Once playback reached end of stream (last ~100ms), position provided by DSP
|
||||
// may be reset/corrupted. This bool is used to avoid that.
|
||||
// Used in main thread and offload callback thread, protected by Mutex
|
||||
// mLock
|
||||
bool mReachedEOS;
|
||||
|
||||
// Set when there is a seek request during pause.
|
||||
// Used in main thread and offload callback thread, protected by Mutex
|
||||
// mLock
|
||||
bool mSeekDuringPause;
|
||||
|
||||
// Seek can be triggered internally or by MediaDecoder. This bool is to
|
||||
// to track seek triggered by MediaDecoder so that we can send back
|
||||
// SeekingStarted and SeekingStopped events.
|
||||
// Used in main thread and offload callback thread, protected by Mutex mLock
|
||||
bool mDispatchSeekEvents;
|
||||
|
||||
// Set when the HTML Audio Element is visible to the user.
|
||||
// Used only in main thread
|
||||
bool mIsElementVisible;
|
||||
@ -155,10 +141,15 @@ private:
|
||||
// mLock
|
||||
int64_t mStartPosUs;
|
||||
|
||||
// Given seek time when there is a request to seek
|
||||
// The target of current seek when there is a request to seek
|
||||
// Used in main thread and offload callback thread, protected by Mutex
|
||||
// mLock
|
||||
int64_t mSeekTimeUs;
|
||||
SeekTarget mSeekTarget;
|
||||
|
||||
// MediaPromise of current seek.
|
||||
// Used in main thread and offload callback thread, protected by Mutex
|
||||
// mLock
|
||||
MediaPromiseHolder<MediaDecoder::SeekPromise> mSeekPromise;
|
||||
|
||||
// Positions obtained from offlaoded tracks (DSP)
|
||||
// Used in main thread and offload callback thread, protected by Mutex
|
||||
@ -221,15 +212,15 @@ private:
|
||||
|
||||
bool IsSeeking();
|
||||
|
||||
// Set mSeekTime to the given position and restart the sink. Actual seek
|
||||
// happens in FillBuffer(). If aDispatchSeekEvents is true, send
|
||||
// Set mSeekTarget to the given position and restart the sink. Actual seek
|
||||
// happens in FillBuffer(). If mSeekPromise is not empty, send
|
||||
// SeekingStarted event always and SeekingStopped event when the play state is
|
||||
// paused to MediaDecoder.
|
||||
// When decoding and playing happens separately, if there is a seek during
|
||||
// pause, we can decode and keep data ready.
|
||||
// In case of offload player, no way to seek during pause. So just fake that
|
||||
// seek is done.
|
||||
status_t SeekTo(int64_t aTimeUs, bool aDispatchSeekEvents = false);
|
||||
status_t DoSeek();
|
||||
|
||||
// Start/Resume the audio sink so that callback will start being called to get
|
||||
// compressed data
|
||||
|
@ -66,6 +66,8 @@ public:
|
||||
{
|
||||
return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
|
||||
}
|
||||
|
||||
virtual nsRefPtr<MediaDecoder::SeekPromise> Seek(SeekTarget aTarget) = 0;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -126,8 +126,9 @@ MediaOmxCommonDecoder::ResumeStateMachine()
|
||||
mAudioOffloadPlayer = nullptr;
|
||||
int64_t timeUsecs = 0;
|
||||
SecondsToUsecs(mCurrentTime, timeUsecs);
|
||||
mRequestedSeekTarget = SeekTarget(timeUsecs, SeekTarget::Accurate);
|
||||
|
||||
mRequestedSeekTarget = SeekTarget(timeUsecs,
|
||||
SeekTarget::Accurate,
|
||||
MediaDecoderEventVisibility::Suppressed);
|
||||
mNextState = mPlayState;
|
||||
ChangeState(PLAY_STATE_LOADING);
|
||||
// exit dormant state
|
||||
@ -193,10 +194,25 @@ MediaOmxCommonDecoder::ChangeState(PlayState aState)
|
||||
// in between
|
||||
MediaDecoder::ChangeState(aState);
|
||||
|
||||
if (mAudioOffloadPlayer) {
|
||||
status_t err = mAudioOffloadPlayer->ChangeState(aState);
|
||||
if (err != OK) {
|
||||
ResumeStateMachine();
|
||||
if (!mAudioOffloadPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
status_t err = mAudioOffloadPlayer->ChangeState(aState);
|
||||
if (err != OK) {
|
||||
ResumeStateMachine();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mPlayState) {
|
||||
case PLAY_STATE_SEEKING:
|
||||
mSeekRequest.Begin(mAudioOffloadPlayer->Seek(mRequestedSeekTarget)
|
||||
->RefableThen(AbstractThread::MainThread(), __func__, static_cast<MediaDecoder*>(this),
|
||||
&MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected));
|
||||
mRequestedSeekTarget.Reset();
|
||||
break;
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
(function(global) {
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
// rather than create a million different IdP configurations and litter the
|
||||
// world with files all containing near-identical code, let's use the hash/URL
|
||||
// fragment as a way of generating instructions for the IdP
|
||||
var instructions = global.location.hash.replace("#", "").split(":");
|
||||
var instructions = global.location.hash.replace('#', '').split(':');
|
||||
function is(target) {
|
||||
return function(instruction) {
|
||||
return instruction === target;
|
||||
@ -16,35 +16,55 @@
|
||||
var path = global.location.pathname;
|
||||
this.protocol =
|
||||
path.substring(path.lastIndexOf('/') + 1) + global.location.hash;
|
||||
this.id = crypto.getRandomValues(new Uint8Array(10)).join('.');
|
||||
}
|
||||
|
||||
function borkResult(result) {
|
||||
if (instructions.some(is("throw"))) {
|
||||
throw new Error('Throwing!');
|
||||
}
|
||||
if (instructions.some(is("fail"))) {
|
||||
return Promise.reject(new Error('Failing!'));
|
||||
}
|
||||
if (instructions.some(is("loginerror"))) {
|
||||
return Promise.reject({
|
||||
name: 'IdpLoginError',
|
||||
loginUrl: 'https://example.com/log/in/here'
|
||||
});
|
||||
}
|
||||
if (instructions.some(is("hang"))) {
|
||||
return new Promise(r => {});
|
||||
}
|
||||
dump('idp: result=' + JSON.stringify(result) + '\n');
|
||||
return Promise.resolve(result);
|
||||
};
|
||||
|
||||
IDPJS.prototype = {
|
||||
getLogin: function() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'https://example.com/.well-known/idp-proxy/idp.sjs?' + this.id);
|
||||
return new Promise(resolve => {
|
||||
xhr.onload = e => resolve(xhr.status === 200);
|
||||
xhr.send();
|
||||
});
|
||||
},
|
||||
checkLogin: function(result) {
|
||||
return this.getLogin()
|
||||
.then(loggedIn => {
|
||||
if (loggedIn) {
|
||||
return result;
|
||||
}
|
||||
return Promise.reject({
|
||||
name: 'IdpLoginError',
|
||||
loginUrl: 'https://example.com/.well-known/idp-proxy/login.html#' +
|
||||
this.id
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
borkResult: function(result) {
|
||||
if (instructions.some(is('throw'))) {
|
||||
throw new Error('Throwing!');
|
||||
}
|
||||
if (instructions.some(is('fail'))) {
|
||||
return Promise.reject(new Error('Failing!'));
|
||||
}
|
||||
if (instructions.some(is('login'))) {
|
||||
return this.checkLogin(result);
|
||||
}
|
||||
if (instructions.some(is('hang'))) {
|
||||
return new Promise(r => {});
|
||||
}
|
||||
dump('idp: result=' + JSON.stringify(result) + '\n');
|
||||
return Promise.resolve(result);
|
||||
},
|
||||
|
||||
_selectUsername: function(usernameHint) {
|
||||
var username = "someone@" + this.domain;
|
||||
var username = 'someone@' + this.domain;
|
||||
if (usernameHint) {
|
||||
var at = usernameHint.indexOf("@");
|
||||
var at = usernameHint.indexOf('@');
|
||||
if (at < 0) {
|
||||
username = usernameHint + "@" + this.domain;
|
||||
username = usernameHint + '@' + this.domain;
|
||||
} else if (usernameHint.substring(at + 1) === this.domain) {
|
||||
username = usernameHint;
|
||||
}
|
||||
@ -58,10 +78,10 @@
|
||||
domain: this.domain,
|
||||
protocol: this.protocol
|
||||
};
|
||||
if (instructions.some(is("bad-assert"))) {
|
||||
if (instructions.some(is('bad-assert'))) {
|
||||
idpDetails = {};
|
||||
}
|
||||
return borkResult({
|
||||
return this.borkResult({
|
||||
idp: idpDetails,
|
||||
assertion: JSON.stringify({
|
||||
username: this._selectUsername(usernameHint),
|
||||
@ -73,17 +93,17 @@
|
||||
validateAssertion: function(assertion, origin) {
|
||||
dump('idp: validateAssertion(' + assertion + ')\n');
|
||||
var assertion = JSON.parse(assertion);
|
||||
if (instructions.some(is("bad-validate"))) {
|
||||
if (instructions.some(is('bad-validate'))) {
|
||||
assertion.contents = {};
|
||||
}
|
||||
return borkResult({
|
||||
return this.borkResult({
|
||||
identity: assertion.username,
|
||||
contents: assertion.contents
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (!instructions.some(is("not_ready"))) {
|
||||
if (!instructions.some(is('not_ready'))) {
|
||||
dump('registering idp.js' + global.location.hash + '\n');
|
||||
global.rtcIdentityProvider.register(new IDPJS());
|
||||
}
|
||||
|
18
dom/media/tests/mochitest/identity/idp.sjs
Normal file
18
dom/media/tests/mochitest/identity/idp.sjs
Normal file
@ -0,0 +1,18 @@
|
||||
function handleRequest(request, response) {
|
||||
var key = '/.well-known/idp-proxy/' + request.queryString;
|
||||
dump(getState(key) + '\n');
|
||||
if (request.method === 'GET') {
|
||||
if (getState(key)) {
|
||||
response.setStatusLine(request.httpVersion, 200, 'OK');
|
||||
} else {
|
||||
response.setStatusLine(request.httpVersion, 404, 'Not Found');
|
||||
}
|
||||
} else if (request.method === 'PUT') {
|
||||
setState(key, 'OK');
|
||||
response.setStatusLine(request.httpVersion, 200, 'OK');
|
||||
} else {
|
||||
response.setStatusLine(request.httpVersion, 406, 'Method Not Allowed');
|
||||
}
|
||||
response.setHeader('Content-Type', 'text/plain;charset=UTF-8');
|
||||
response.write('OK');
|
||||
}
|
31
dom/media/tests/mochitest/identity/login.html
Normal file
31
dom/media/tests/mochitest/identity/login.html
Normal file
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Identity Provider Login</title>
|
||||
<script type="application/javascript">
|
||||
window.onload = () => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", "https://example.com/.well-known/idp-proxy/idp.sjs?" +
|
||||
window.location.hash.replace('#', ''));
|
||||
xhr.onload = () => {
|
||||
var isFramed = (window !== window.top);
|
||||
var parent = isFramed ? window.parent : window.opener;
|
||||
// Using '*' is cheating, but that's OK.
|
||||
parent.postMessage('LOGINDONE', '*');
|
||||
var done = document.createElement('div');
|
||||
|
||||
done.textContent = 'Done';
|
||||
document.body.appendChild(done);
|
||||
|
||||
if (!isFramed) {
|
||||
window.close();
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div>Logging in...</div>
|
||||
</body>
|
||||
</html>
|
@ -27,6 +27,11 @@ support-files =
|
||||
[test_setIdentityProviderWithErrors.html]
|
||||
[test_peerConnection_peerIdentity.html]
|
||||
[test_peerConnection_asymmetricIsolation.html]
|
||||
[test_loginNeeded.html]
|
||||
support-files =
|
||||
/.well-known/idp-proxy/login.html
|
||||
/.well-known/idp-proxy/idp.sjs
|
||||
|
||||
|
||||
# Bug 950317: Hack for making a cleanup hook after finishing all WebRTC cases
|
||||
[../test_zmedia_cleanup.html]
|
||||
|
@ -12,7 +12,7 @@
|
||||
function getIdentityAssertion(fpArray) {
|
||||
var Cu = SpecialPowers.Cu;
|
||||
var rtcid = Cu.import('resource://gre/modules/media/IdpSandbox.jsm');
|
||||
var sandbox = new rtcid.IdpSandbox('example.com', 'idp.js');
|
||||
var sandbox = new rtcid.IdpSandbox('example.com', 'idp.js', window.document);
|
||||
return sandbox.start()
|
||||
.then(idp => SpecialPowers.wrap(idp)
|
||||
.generateAssertion(JSON.stringify({ fingerprint: fpArray }),
|
||||
|
@ -55,11 +55,12 @@ function theTest() {
|
||||
},
|
||||
|
||||
function PC_LOCAL_IDP_LOGIN_ERROR(t) {
|
||||
return getAssertion(t, '#loginerror')
|
||||
.then(a => ok(false, '#loginerror should not work'),
|
||||
return getAssertion(t, '#login')
|
||||
.then(a => ok(false, '#login should not work'),
|
||||
e => {
|
||||
is(e.name, 'IdpLoginError', 'name is IdpLoginError');
|
||||
is(t.pcLocal._pc.idpLoginUrl, 'https://example.com/log/in/here',
|
||||
is(t.pcLocal._pc.idpLoginUrl.split('#')[0],
|
||||
'https://example.com/.well-known/idp-proxy/login.html',
|
||||
'got the right login URL from the IdP');
|
||||
});
|
||||
},
|
||||
|
@ -26,7 +26,7 @@ function test_domain_sandbox() {
|
||||
'', 12, null, diabolical, true ];
|
||||
domains.forEach(function(domain) {
|
||||
try {
|
||||
var idp = new IdpSandbox(domain);
|
||||
var idp = new IdpSandbox(domain, undefined, window.document);
|
||||
ok(false, 'IdpSandbox allowed a bad domain: ' + domain);
|
||||
} catch (e) {
|
||||
var str = (typeof domain === 'string') ? domain : typeof domain;
|
||||
@ -40,7 +40,7 @@ function test_protocol_sandbox() {
|
||||
'\\evil', '%5cevil', 12, true, {} ];
|
||||
protos.forEach(function(proto) {
|
||||
try {
|
||||
var idp = new IdpSandbox('example.com', proto);
|
||||
var idp = new IdpSandbox('example.com', proto, window.document);
|
||||
ok(false, 'IdpSandbox allowed a bad protocol: ' + proto);
|
||||
} catch (e) {
|
||||
var str = (typeof proto === 'string') ? proto : typeof proto;
|
||||
@ -56,7 +56,7 @@ function idpName(hash) {
|
||||
function makeSandbox(js) {
|
||||
var name = js || idpName();
|
||||
info('Creating a sandbox for the protocol: ' + name);
|
||||
var sandbox = new IdpSandbox('example.com', name);
|
||||
var sandbox = new IdpSandbox('example.com', name, window.document);
|
||||
return sandbox.start().then(idp => SpecialPowers.wrap(idp));
|
||||
}
|
||||
|
||||
|
71
dom/media/tests/mochitest/identity/test_loginNeeded.html
Normal file
71
dom/media/tests/mochitest/identity/test_loginNeeded.html
Normal file
@ -0,0 +1,71 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<script type="application/javascript">var scriptRelativePath = "../";</script>
|
||||
<script type="application/javascript" src="../pc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
createHTML({
|
||||
title: 'RTCPeerConnection identity with login',
|
||||
bug: '1153314'
|
||||
});
|
||||
|
||||
function waitForLoginDone() {
|
||||
return new Promise(resolve => {
|
||||
window.addEventListener('message', function listener(e) {
|
||||
is(e.origin, 'https://example.com', 'got the right message origin');
|
||||
is(e.data, 'LOGINDONE', 'got the right message');
|
||||
window.removeEventListener('message', listener);
|
||||
resolve();
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
|
||||
function checkLogin(t, name, onLoginNeeded) {
|
||||
t.pcLocal.setIdentityProvider('example.com', 'idp.js#login:' + name);
|
||||
return t.pcLocal._pc.getIdentityAssertion()
|
||||
.then(a => ok(false, 'should request login'),
|
||||
e => {
|
||||
is(e.name, 'IdpLoginError', 'name is IdpLoginError');
|
||||
is(t.pcLocal._pc.idpLoginUrl.split('#')[0],
|
||||
'https://example.com/.well-known/idp-proxy/login.html',
|
||||
'got the right login URL from the IdP');
|
||||
return t.pcLocal._pc.idpLoginUrl;
|
||||
})
|
||||
.then(onLoginNeeded)
|
||||
.then(waitForLoginDone)
|
||||
.then(() => t.pcLocal._pc.getIdentityAssertion())
|
||||
.then(a => ok(a, 'got assertion'));
|
||||
}
|
||||
|
||||
function theTest() {
|
||||
var test = new PeerConnectionTest();
|
||||
test.setMediaConstraints([{audio: true}], [{audio: true}]);
|
||||
test.chain.removeAfter('PC_REMOTE_CHECK_INITIAL_SIGNALINGSTATE');
|
||||
test.chain.append([
|
||||
function PC_LOCAL_IDENTITY_ASSERTION_WITH_IFRAME_LOGIN(t) {
|
||||
return checkLogin(t, 'iframe', loginUrl => {
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('src', loginUrl);
|
||||
iframe.frameBorder = 0;
|
||||
iframe.width = 400;
|
||||
iframe.height = 60;
|
||||
document.getElementById('display').appendChild(iframe);
|
||||
});
|
||||
},
|
||||
function PC_LOCAL_IDENTITY_ASSERTION_WITH_WINDOW_LOGIN(t) {
|
||||
return checkLogin(t, 'openwin', loginUrl => {
|
||||
window.open(loginUrl, 'login', 'width=400,height=60');
|
||||
});
|
||||
}
|
||||
]);
|
||||
test.run();
|
||||
}
|
||||
runNetworkTest(theTest);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -10,6 +10,7 @@ interface WindowProxy;
|
||||
interface nsISupports;
|
||||
interface URI;
|
||||
interface nsIDocShell;
|
||||
interface nsILoadGroup;
|
||||
|
||||
enum VisibilityState { "hidden", "visible" };
|
||||
|
||||
@ -354,6 +355,8 @@ partial interface Document {
|
||||
[ChromeOnly] readonly attribute nsIDocShell? docShell;
|
||||
|
||||
[ChromeOnly] readonly attribute DOMString contentLanguage;
|
||||
|
||||
[ChromeOnly] readonly attribute nsILoadGroup? documentLoadGroup;
|
||||
};
|
||||
|
||||
// Extension to give chrome JS the ability to determine when a document was
|
||||
|
@ -47,6 +47,7 @@ interface NavigatorID {
|
||||
readonly attribute DOMString product; // constant "Gecko"
|
||||
|
||||
// Everyone but WebKit/Blink supports this. See bug 679971.
|
||||
[Exposed=Window]
|
||||
boolean taintEnabled(); // constant false
|
||||
};
|
||||
|
||||
|
@ -3996,17 +3996,16 @@ WorkerPrivateParent<Derived>::SetBaseURI(nsIURI* aBaseURI)
|
||||
mLocationInfo.mHref.Truncate();
|
||||
}
|
||||
|
||||
if (NS_FAILED(aBaseURI->GetHost(mLocationInfo.mHostname))) {
|
||||
mLocationInfo.mHostname.Truncate();
|
||||
}
|
||||
mLocationInfo.mHostname.Truncate();
|
||||
nsContentUtils::GetHostOrIPv6WithBrackets(aBaseURI, mLocationInfo.mHostname);
|
||||
|
||||
if (NS_FAILED(aBaseURI->GetPath(mLocationInfo.mPathname))) {
|
||||
nsCOMPtr<nsIURL> url(do_QueryInterface(aBaseURI));
|
||||
if (!url || NS_FAILED(url->GetFilePath(mLocationInfo.mPathname))) {
|
||||
mLocationInfo.mPathname.Truncate();
|
||||
}
|
||||
|
||||
nsCString temp;
|
||||
|
||||
nsCOMPtr<nsIURL> url(do_QueryInterface(aBaseURI));
|
||||
if (url && NS_SUCCEEDED(url->GetQuery(temp)) && !temp.IsEmpty()) {
|
||||
mLocationInfo.mSearch.Assign('?');
|
||||
mLocationInfo.mSearch.Append(temp);
|
||||
|
@ -11,7 +11,6 @@ var supportedProps = [
|
||||
{ name: "getDataStores", b2g: true },
|
||||
"platform",
|
||||
"product",
|
||||
"taintEnabled",
|
||||
"userAgent",
|
||||
"onLine",
|
||||
"language",
|
||||
@ -66,9 +65,7 @@ function startTest(isB2G) {
|
||||
|
||||
obj = { name: prop };
|
||||
|
||||
if (prop === "taintEnabled") {
|
||||
obj.value = navigator[prop]();
|
||||
} else if (prop === "getDataStores") {
|
||||
if (prop === "getDataStores") {
|
||||
obj.value = typeof navigator[prop];
|
||||
} else {
|
||||
obj.value = navigator[prop];
|
||||
|
@ -35,11 +35,6 @@ Tests of DOM Worker Navigator
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.name === "taintEnabled") {
|
||||
is(navigator[args.name](), args.value, args.name + "() returns false.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.name === "getDataStores") {
|
||||
var type = typeof navigator[args.name];
|
||||
is(type, args.value, "getDataStores() exists and it's a function.");
|
||||
|
@ -111,7 +111,7 @@ onmessage = function() {
|
||||
{ url: 'http://example.com/carrot#question%3f',
|
||||
base: undefined,
|
||||
error: false,
|
||||
hash: '#question%3f'
|
||||
hash: '#question?'
|
||||
},
|
||||
{ url: 'https://example.com:4443?',
|
||||
base: undefined,
|
||||
|
@ -55,8 +55,8 @@
|
||||
|
||||
#include "prmjtime.h"
|
||||
|
||||
#include "asmjs/AsmJSModule.h"
|
||||
#include "jit/AtomicOperations.h"
|
||||
|
||||
#include "js/Class.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/SharedTypedArrayObject.h"
|
||||
|
@ -1503,7 +1503,7 @@ js::testingFunc_inJit(JSContext* cx, unsigned argc, jsval* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!IsBaselineEnabled(cx)) {
|
||||
if (!jit::IsBaselineEnabled(cx)) {
|
||||
JSString* error = JS_NewStringCopyZ(cx, "Baseline is disabled.");
|
||||
if(!error)
|
||||
return false;
|
||||
@ -1531,7 +1531,7 @@ js::testingFunc_inIon(JSContext* cx, unsigned argc, jsval* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!IsIonEnabled(cx)) {
|
||||
if (!jit::IsIonEnabled(cx)) {
|
||||
JSString* error = JS_NewStringCopyZ(cx, "Ion is disabled.");
|
||||
if (!error)
|
||||
return false;
|
||||
|
@ -7,8 +7,9 @@
|
||||
#ifndef ds_IdValuePair_h
|
||||
#define ds_IdValuePair_h
|
||||
|
||||
#include "NamespaceImports.h"
|
||||
#include "jsapi.h"
|
||||
|
||||
#include "NamespaceImports.h"
|
||||
#include "js/Id.h"
|
||||
|
||||
namespace js {
|
||||
|
@ -977,13 +977,13 @@ BytecodeEmitter::leaveNestedScope(StmtInfoBCE* stmt)
|
||||
if (!emit1(stmt->isBlockScope ? JSOP_DEBUGLEAVEBLOCK : JSOP_LEAVEWITH))
|
||||
return false;
|
||||
|
||||
blockScopeList.recordEnd(blockScopeIndex, offset());
|
||||
|
||||
if (stmt->isBlockScope && stmt->staticScope->as<StaticBlockObject>().needsClone()) {
|
||||
if (!emit1(JSOP_POPBLOCKSCOPE))
|
||||
return false;
|
||||
}
|
||||
|
||||
blockScopeList.recordEnd(blockScopeIndex, offset());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -346,7 +346,7 @@ ArenaLists::allocateFromArena(JS::Zone* zone, AllocKind thingKind,
|
||||
AutoMaybeStartBackgroundAllocation& maybeStartBGAlloc)
|
||||
{
|
||||
JSRuntime* rt = zone->runtimeFromAnyThread();
|
||||
Maybe<AutoLockGC> maybeLock;
|
||||
mozilla::Maybe<AutoLockGC> maybeLock;
|
||||
|
||||
// See if we can proceed without taking the GC lock.
|
||||
if (backgroundFinalizeState[thingKind] != BFS_DONE)
|
||||
|
9
js/src/jit-test/tests/debug/Frame-environment-05.js
Normal file
9
js/src/jit-test/tests/debug/Frame-environment-05.js
Normal file
@ -0,0 +1,9 @@
|
||||
// Test that Debugger.Frame.prototype.environment works at all pcs of a script
|
||||
// with an aliased block scope.
|
||||
|
||||
var g = newGlobal();
|
||||
var dbg = new Debugger(g);
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
frame.onStep = (function () { frame.environment; });
|
||||
};
|
||||
g.eval("debugger; for (let i of [1,2,3]) print(i);");
|
8
js/src/jit-test/tests/debug/bug1147939.js
Normal file
8
js/src/jit-test/tests/debug/bug1147939.js
Normal file
@ -0,0 +1,8 @@
|
||||
// |jit-test| error: TypeError
|
||||
var g = newGlobal();
|
||||
g.debuggeeGlobal = this;
|
||||
g.eval("(" + function () {
|
||||
dbg = new Debugger(debuggeeGlobal);
|
||||
dbg.onExceptionUnwind = Map;
|
||||
} + ")();");
|
||||
throw new Error("oops");
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "jit/BacktrackingAllocator.h"
|
||||
#include "jit/LIR.h"
|
||||
#include "jit/MIR.h"
|
||||
#include "jit/MIRGraph.h"
|
||||
|
@ -1532,8 +1532,8 @@ JitcodeIonTable::WriteIonTable(CompactBufferWriter& writer,
|
||||
JS_PUBLIC_API(JS::ProfilingFrameIterator::FrameKind)
|
||||
JS::GetProfilingFrameKindFromNativeAddr(JSRuntime* rt, void* addr)
|
||||
{
|
||||
JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable();
|
||||
JitcodeGlobalEntry entry;
|
||||
js::jit::JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable();
|
||||
js::jit::JitcodeGlobalEntry entry;
|
||||
table->lookupInfallible(addr, &entry, rt);
|
||||
MOZ_ASSERT(entry.isIon() || entry.isIonCache() || entry.isBaseline());
|
||||
|
||||
|
@ -9,8 +9,10 @@
|
||||
# error "Wrong architecture. Only x86 and x64 should build this file!"
|
||||
#endif
|
||||
|
||||
#include "jit/RegisterSets.h"
|
||||
|
||||
const char*
|
||||
FloatRegister::name() const {
|
||||
js::jit::FloatRegister::name() const {
|
||||
static const char* const names[] = {
|
||||
|
||||
#ifdef JS_CODEGEN_X64
|
||||
@ -38,8 +40,8 @@ FloatRegister::name() const {
|
||||
return names[size_t(code())];
|
||||
}
|
||||
|
||||
FloatRegisterSet
|
||||
FloatRegister::ReduceSetForPush(const FloatRegisterSet& s)
|
||||
js::jit::FloatRegisterSet
|
||||
js::jit::FloatRegister::ReduceSetForPush(const FloatRegisterSet& s)
|
||||
{
|
||||
SetType bits = s.bits();
|
||||
|
||||
@ -58,7 +60,7 @@ FloatRegister::ReduceSetForPush(const FloatRegisterSet& s)
|
||||
}
|
||||
|
||||
uint32_t
|
||||
FloatRegister::GetPushSizeInBytes(const FloatRegisterSet& s)
|
||||
js::jit::FloatRegister::GetPushSizeInBytes(const FloatRegisterSet& s)
|
||||
{
|
||||
SetType all = s.bits();
|
||||
SetType float32x4Set =
|
||||
@ -92,7 +94,7 @@ FloatRegister::GetPushSizeInBytes(const FloatRegisterSet& s)
|
||||
+ count32b * sizeof(float);
|
||||
}
|
||||
uint32_t
|
||||
FloatRegister::getRegisterDumpOffsetInBytes()
|
||||
js::jit::FloatRegister::getRegisterDumpOffsetInBytes()
|
||||
{
|
||||
return uint32_t(encoding()) * sizeof(FloatRegisters::RegisterContent);
|
||||
}
|
||||
|
@ -11,6 +11,10 @@
|
||||
# error "Unsupported architecture!"
|
||||
#endif
|
||||
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "jit/x86-shared/Constants-x86-shared.h"
|
||||
|
||||
namespace js {
|
||||
|
@ -7,6 +7,11 @@
|
||||
#ifndef jit_x86_shared_Constants_x86_shared_h
|
||||
#define jit_x86_shared_Constants_x86_shared_h
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
||||
|
@ -45,7 +45,7 @@ BEGIN_TEST(testForwardSetProperty)
|
||||
|
||||
// Non-strict setter
|
||||
|
||||
ObjectOpResult result;
|
||||
JS::ObjectOpResult result;
|
||||
CHECK(JS_ForwardSetPropertyTo(cx, obj2, prop, setval, v3, result));
|
||||
CHECK(result);
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
#include "jsapi-tests/tests.h"
|
||||
|
||||
using mozilla::ArrayLength;
|
||||
|
||||
BEGIN_TEST(testJSEvaluateScript)
|
||||
{
|
||||
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
|
||||
|
@ -7,6 +7,7 @@
|
||||
#ifndef jsapi_tests_tests_h
|
||||
#define jsapi_tests_tests_h
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/TypeTraits.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
@ -125,7 +125,7 @@ class AutoClearPendingException
|
||||
{ }
|
||||
|
||||
~AutoClearPendingException() {
|
||||
cx->clearPendingException();
|
||||
JS_ClearPendingException(cx);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1753,7 +1753,13 @@ FindStartPC(JSContext* cx, const FrameIter& iter, int spindex, int skipStackHits
|
||||
|
||||
if (spindex == JSDVG_SEARCH_STACK) {
|
||||
size_t index = iter.numFrameSlots();
|
||||
MOZ_ASSERT(index >= size_t(parser.stackDepthAtPC(current)));
|
||||
|
||||
// The decompiler may be called from inside functions that are not
|
||||
// called from script, but via the C++ API directly, such as
|
||||
// Invoke. In that case, the youngest script frame may have a
|
||||
// completely unrelated pc and stack depth, so we give up.
|
||||
if (index < size_t(parser.stackDepthAtPC(current)))
|
||||
return true;
|
||||
|
||||
// We search from fp->sp to base to find the most recently calculated
|
||||
// value matching v under assumption that it is the value that caused
|
||||
|
@ -1634,6 +1634,9 @@ class JSScript : public js::gc::TenuredCell
|
||||
return arr->vector[index];
|
||||
}
|
||||
|
||||
// The following 3 functions find the static scope just before the
|
||||
// execution of the instruction pointed to by pc.
|
||||
|
||||
js::NestedScopeObject* getStaticBlockScope(jsbytecode* pc);
|
||||
|
||||
// Returns the innermost static scope at pc if it falls within the extent
|
||||
|
@ -2202,6 +2202,30 @@ TryNotes(JSContext* cx, HandleScript script, Sprinter* sp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
BlockNotes(JSContext* cx, HandleScript script, Sprinter* sp)
|
||||
{
|
||||
if (!script->hasBlockScopes())
|
||||
return true;
|
||||
|
||||
Sprint(sp, "\nBlock table:\n index parent start end\n");
|
||||
|
||||
BlockScopeArray* scopes = script->blockScopes();
|
||||
for (uint32_t i = 0; i < scopes->length; i++) {
|
||||
const BlockScopeNote* note = &scopes->vector[i];
|
||||
if (note->index == BlockScopeNote::NoBlockScopeIndex)
|
||||
Sprint(sp, "%8s ", "(none)");
|
||||
else
|
||||
Sprint(sp, "%8u ", note->index);
|
||||
if (note->parent == BlockScopeNote::NoBlockScopeIndex)
|
||||
Sprint(sp, "%8s ", "(none)");
|
||||
else
|
||||
Sprint(sp, "%8u ", note->parent);
|
||||
Sprint(sp, "%8u %8u\n", note->start, note->start + note->length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
DisassembleScript(JSContext* cx, HandleScript script, HandleFunction fun, bool lines,
|
||||
bool recursive, Sprinter* sp)
|
||||
@ -2229,6 +2253,7 @@ DisassembleScript(JSContext* cx, HandleScript script, HandleFunction fun, bool l
|
||||
return false;
|
||||
SrcNotes(cx, script, sp);
|
||||
TryNotes(cx, script, sp);
|
||||
BlockNotes(cx, script, sp);
|
||||
|
||||
if (recursive && script->hasObjects()) {
|
||||
ObjectArray* objects = script->objects();
|
||||
@ -2299,6 +2324,7 @@ DisassembleToSprinter(JSContext* cx, unsigned argc, jsval* vp, Sprinter* sprinte
|
||||
return false;
|
||||
SrcNotes(cx, script, sprinter);
|
||||
TryNotes(cx, script, sprinter);
|
||||
BlockNotes(cx, script, sprinter);
|
||||
}
|
||||
} else {
|
||||
for (unsigned i = 0; i < p.argc; i++) {
|
||||
@ -4314,7 +4340,7 @@ SetSharedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
class SprintOptimizationTypeInfoOp : public ForEachTrackedOptimizationTypeInfoOp
|
||||
class SprintOptimizationTypeInfoOp : public JS::ForEachTrackedOptimizationTypeInfoOp
|
||||
{
|
||||
Sprinter* sp;
|
||||
bool startedTypes_;
|
||||
@ -4345,7 +4371,7 @@ class SprintOptimizationTypeInfoOp : public ForEachTrackedOptimizationTypeInfoOp
|
||||
Sprint(sp, "},");
|
||||
}
|
||||
|
||||
void operator()(TrackedTypeSite site, const char* mirType) override {
|
||||
void operator()(JS::TrackedTypeSite site, const char* mirType) override {
|
||||
if (startedTypes_) {
|
||||
// Clear trailing ,
|
||||
if ((*sp)[sp->getOffset() - 1] == ',')
|
||||
@ -4361,7 +4387,7 @@ class SprintOptimizationTypeInfoOp : public ForEachTrackedOptimizationTypeInfoOp
|
||||
}
|
||||
};
|
||||
|
||||
class SprintOptimizationAttemptsOp : public ForEachTrackedOptimizationAttemptOp
|
||||
class SprintOptimizationAttemptsOp : public JS::ForEachTrackedOptimizationAttemptOp
|
||||
{
|
||||
Sprinter* sp;
|
||||
|
||||
@ -4370,7 +4396,7 @@ class SprintOptimizationAttemptsOp : public ForEachTrackedOptimizationAttemptOp
|
||||
: sp(sp)
|
||||
{ }
|
||||
|
||||
void operator()(TrackedStrategy strategy, TrackedOutcome outcome) override {
|
||||
void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) override {
|
||||
Sprint(sp, "{\"strategy\":\"%s\",\"outcome\":\"%s\"},",
|
||||
TrackedStrategyString(strategy), TrackedOutcomeString(outcome));
|
||||
}
|
||||
|
@ -2490,6 +2490,7 @@ js::GetDebugScopeForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
|
||||
assertSameCompartment(cx, frame);
|
||||
if (CanUseDebugScopeMaps(cx) && !DebugScopes::updateLiveScopes(cx))
|
||||
return nullptr;
|
||||
|
||||
ScopeIter si(cx, frame, pc);
|
||||
return GetDebugScope(cx, si);
|
||||
}
|
||||
|
@ -382,10 +382,6 @@ nsRubyBaseContainerFrame::Reflow(nsPresContext* aPresContext,
|
||||
};
|
||||
nscoord spanISize = ReflowSpans(reflowState);
|
||||
isize = std::max(isize, spanISize);
|
||||
if (isize > aReflowState.AvailableISize() &&
|
||||
aReflowState.mLineLayout->HasOptionalBreakPosition()) {
|
||||
aStatus = NS_INLINE_LINE_BREAK_BEFORE();
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < rtcCount; i++) {
|
||||
|
@ -803,8 +803,6 @@ marquee[direction="up"], marquee[direction="down"] {
|
||||
}
|
||||
rt {
|
||||
display: ruby-text;
|
||||
font-variant-east-asian: ruby;
|
||||
text-emphasis: none;
|
||||
}
|
||||
rtc {
|
||||
display: ruby-text-container;
|
||||
@ -813,6 +811,12 @@ marquee[direction="up"], marquee[direction="down"] {
|
||||
white-space: nowrap;
|
||||
font-size: 50%;
|
||||
line-height: 1;
|
||||
font-variant-east-asian: ruby;
|
||||
}
|
||||
@supports (text-emphasis: none) {
|
||||
rtc, rt {
|
||||
text-emphasis: none;
|
||||
}
|
||||
}
|
||||
rtc:lang(zh), rt:lang(zh) {
|
||||
ruby-align: center;
|
||||
|
@ -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);
|
||||
},
|
||||
|
||||
|
||||
|
@ -171,7 +171,7 @@ pref("dom.undo_manager.enabled", false);
|
||||
|
||||
// Whether URL,nsLocation,Link::GetHash should be percent encoded
|
||||
// in setter and percent decoded in getter (old behaviour = true)
|
||||
pref("dom.url.encode_decode_hash", false);
|
||||
pref("dom.url.encode_decode_hash", true);
|
||||
|
||||
// Whether to run add-on code in different compartments from browser code. This
|
||||
// causes a separate compartment for each (addon, global) combination, which may
|
||||
|
@ -342,3 +342,17 @@
|
||||
[Parsing: <x> against <test:test>]
|
||||
expected: FAIL
|
||||
|
||||
[Parsing: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>]
|
||||
expected: FAIL
|
||||
|
||||
[Parsing: <#\xce\xb2> against <http://example.org/foo/bar>]
|
||||
expected: FAIL
|
||||
|
||||
[Parsing: <http://www.google.com/foo?bar=baz# \xc2\xbb> against <about:blank>]
|
||||
expected: FAIL
|
||||
|
||||
[Parsing: <#β> against <http://example.org/foo/bar>]
|
||||
expected: FAIL
|
||||
|
||||
[Parsing: <http://www.google.com/foo?bar=baz# »> against <about:blank>]
|
||||
expected: FAIL
|
||||
|
@ -361,3 +361,17 @@
|
||||
[Parsing: <x> against <test:test>]
|
||||
expected: FAIL
|
||||
|
||||
[Parsing: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>]
|
||||
expected: FAIL
|
||||
|
||||
[Parsing: <#\xce\xb2> against <http://example.org/foo/bar>]
|
||||
expected: FAIL
|
||||
|
||||
[Parsing: <http://www.google.com/foo?bar=baz# \xc2\xbb> against <about:blank>]
|
||||
expected: FAIL
|
||||
|
||||
[Parsing: <#β> against <http://example.org/foo/bar>]
|
||||
expected: FAIL
|
||||
|
||||
[Parsing: <http://www.google.com/foo?bar=baz# »> against <about:blank>]
|
||||
expected: FAIL
|
||||
|
@ -2,4 +2,5 @@
|
||||
type: testharness
|
||||
[ MessageChannel: port message queue is initially disabled ]
|
||||
expected: FAIL
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=952139
|
||||
|
||||
|
@ -2,4 +2,5 @@
|
||||
type: testharness
|
||||
[ MessageChannel: port.onmessage enables message queue ]
|
||||
expected: FAIL
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=952139
|
||||
|
||||
|
@ -0,0 +1,4 @@
|
||||
[WorkerLocation_hash_encoding.htm]
|
||||
type: testharness
|
||||
[ WorkerLocation.hash with url encoding string ]
|
||||
expected: FAIL
|
@ -2,6 +2,7 @@
|
||||
type: testharness
|
||||
[WorkerGlobalScope interface: attribute onlanguagechange]
|
||||
expected: FAIL
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1154779
|
||||
|
||||
[WorkerGlobalScope interface: operation importScripts(DOMString)]
|
||||
expected: FAIL
|
||||
@ -29,6 +30,7 @@
|
||||
|
||||
[WorkerGlobalScope interface: self must inherit property "onlanguagechange" with the proper type (4)]
|
||||
expected: FAIL
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1154779
|
||||
|
||||
[WorkerGlobalScope interface: calling importScripts(DOMString) on self with too few arguments must throw TypeError]
|
||||
expected: FAIL
|
||||
|
@ -1,5 +0,0 @@
|
||||
[members.html]
|
||||
type: testharness
|
||||
[members of WorkerLocation]
|
||||
expected: FAIL
|
||||
|
@ -1,5 +0,0 @@
|
||||
[redirect.html]
|
||||
type: testharness
|
||||
[location with a worker in separate file that redirects]
|
||||
expected: FAIL
|
||||
|
@ -1,5 +0,0 @@
|
||||
[setting-members.html]
|
||||
type: testharness
|
||||
[setting members of WorkerLocation]
|
||||
expected: FAIL
|
||||
|
@ -1,5 +0,0 @@
|
||||
[worker-separate-file.html]
|
||||
type: testharness
|
||||
[location with a worker in separate file]
|
||||
expected: FAIL
|
||||
|
@ -1,5 +0,0 @@
|
||||
[007.html]
|
||||
type: testharness
|
||||
[readonlyness of members of Navigator]
|
||||
expected: FAIL
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user