Merge mozilla-central to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-04-16 12:52:54 +02:00
commit 66755840fb
107 changed files with 1977 additions and 791 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -66,6 +66,8 @@ public:
{
return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
}
virtual nsRefPtr<MediaDecoder::SeekPromise> Seek(SeekTarget aTarget) = 0;
};
} // namespace mozilla

View File

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

View File

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

View 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');
}

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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);");

View 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");

View File

@ -8,6 +8,7 @@
#include <stdarg.h>
#include "jit/BacktrackingAllocator.h"
#include "jit/LIR.h"
#include "jit/MIR.h"
#include "jit/MIRGraph.h"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,8 @@
#include "jsapi-tests/tests.h"
using mozilla::ArrayLength;
BEGIN_TEST(testJSEvaluateScript)
{
JS::RootedObject obj(cx, JS_NewPlainObject(cx));

View File

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

View File

@ -125,7 +125,7 @@ class AutoClearPendingException
{ }
~AutoClearPendingException() {
cx->clearPendingException();
JS_ClearPendingException(cx);
}
};

View File

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

View File

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

View File

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

View File

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

View File

@ -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++) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,4 +2,5 @@
type: testharness
[ MessageChannel: port.onmessage enables message queue ]
expected: FAIL
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=952139

View File

@ -0,0 +1,4 @@
[WorkerLocation_hash_encoding.htm]
type: testharness
[ WorkerLocation.hash with url encoding string ]
expected: FAIL

View File

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

View File

@ -1,5 +0,0 @@
[members.html]
type: testharness
[members of WorkerLocation]
expected: FAIL

View File

@ -1,5 +0,0 @@
[redirect.html]
type: testharness
[location with a worker in separate file that redirects]
expected: FAIL

View File

@ -1,5 +0,0 @@
[setting-members.html]
type: testharness
[setting members of WorkerLocation]
expected: FAIL

View File

@ -1,5 +0,0 @@
[worker-separate-file.html]
type: testharness
[location with a worker in separate file]
expected: FAIL

View File

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