Merge m-c to b-i

This commit is contained in:
Phil Ringnalda 2015-05-25 19:30:00 -07:00
commit 8525f85258
208 changed files with 5483 additions and 1874 deletions

View File

@ -14,7 +14,7 @@ loader.lazyRequireGetter(this, "Poller",
loader.lazyRequireGetter(this, "CompatUtils", loader.lazyRequireGetter(this, "CompatUtils",
"devtools/performance/compatibility"); "devtools/performance/compatibility");
loader.lazyRequireGetter(this, "RecordingUtils", loader.lazyRequireGetter(this, "RecordingUtils",
"devtools/performance/recording-utils", true); "devtools/performance/recording-utils");
loader.lazyRequireGetter(this, "TimelineFront", loader.lazyRequireGetter(this, "TimelineFront",
"devtools/server/actors/timeline", true); "devtools/server/actors/timeline", true);
loader.lazyRequireGetter(this, "MemoryFront", loader.lazyRequireGetter(this, "MemoryFront",
@ -207,10 +207,6 @@ ProfilerFrontFacade.prototype = {
toString: () => "[object ProfilerFrontFacade]" toString: () => "[object ProfilerFrontFacade]"
}; };
// Bind all the methods that directly proxy to the actor
PROFILER_ACTOR_METHODS.forEach(method => ProfilerFrontFacade.prototype[method] = CompatUtils.actorCompatibilityBridge(method));
exports.ProfilerFront = ProfilerFrontFacade;
/** /**
* Constructor for a facade around an underlying TimelineFront. * Constructor for a facade around an underlying TimelineFront.
*/ */
@ -258,10 +254,6 @@ TimelineFrontFacade.prototype = {
toString: () => "[object TimelineFrontFacade]" toString: () => "[object TimelineFrontFacade]"
}; };
// Bind all the methods that directly proxy to the actor
TIMELINE_ACTOR_METHODS.forEach(method => TimelineFrontFacade.prototype[method] = CompatUtils.actorCompatibilityBridge(method));
exports.TimelineFront = TimelineFrontFacade;
/** /**
* Constructor for a facade around an underlying ProfilerFront. * Constructor for a facade around an underlying ProfilerFront.
*/ */
@ -378,5 +370,10 @@ MemoryFrontFacade.prototype = {
}; };
// Bind all the methods that directly proxy to the actor // Bind all the methods that directly proxy to the actor
MEMORY_ACTOR_METHODS.forEach(method => MemoryFrontFacade.prototype[method] = CompatUtils.actorCompatibilityBridge(method)); PROFILER_ACTOR_METHODS.forEach(m => ProfilerFrontFacade.prototype[m] = CompatUtils.actorCompatibilityBridge(m));
TIMELINE_ACTOR_METHODS.forEach(m => TimelineFrontFacade.prototype[m] = CompatUtils.actorCompatibilityBridge(m));
MEMORY_ACTOR_METHODS.forEach(m => MemoryFrontFacade.prototype[m] = CompatUtils.actorCompatibilityBridge(m));
exports.ProfilerFront = ProfilerFrontFacade;
exports.TimelineFront = TimelineFrontFacade;
exports.MemoryFront = MemoryFrontFacade; exports.MemoryFront = MemoryFrontFacade;

View File

@ -3,8 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict"; "use strict";
const { Task } = require("resource://gre/modules/Task.jsm");
loader.lazyRequireGetter(this, "promise"); loader.lazyRequireGetter(this, "promise");
loader.lazyRequireGetter(this, "EventEmitter", loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter"); "devtools/toolkit/event-emitter");
@ -36,7 +34,6 @@ function MockMemoryFront () {
["getAllocations", createMockAllocations], ["getAllocations", createMockAllocations],
]); ]);
} }
exports.MockMemoryFront = MockMemoryFront;
function MockTimelineFront () { function MockTimelineFront () {
MockFront.call(this, [ MockFront.call(this, [
@ -45,7 +42,6 @@ function MockTimelineFront () {
["stop", 0], ["stop", 0],
]); ]);
} }
exports.MockTimelineFront = MockTimelineFront;
/** /**
* Create a fake allocations object, to be used with the MockMemoryFront * Create a fake allocations object, to be used with the MockMemoryFront
@ -86,7 +82,6 @@ function memoryActorSupported (target) {
// but no memory actor (like addon debugging in Gecko 38+) // but no memory actor (like addon debugging in Gecko 38+)
return !!target.getTrait("memoryActorAllocations") && target.hasActor("memory"); return !!target.getTrait("memoryActorAllocations") && target.hasActor("memory");
} }
exports.memoryActorSupported = Task.async(memoryActorSupported);
/** /**
* Takes a TabTarget, and checks existence of a TimelineActor on * Takes a TabTarget, and checks existence of a TimelineActor on
@ -104,7 +99,6 @@ function timelineActorSupported(target) {
return target.hasActor("timeline"); return target.hasActor("timeline");
} }
exports.timelineActorSupported = Task.async(timelineActorSupported);
/** /**
* Returns a promise resolving to the location of the profiler actor * Returns a promise resolving to the location of the profiler actor
@ -131,7 +125,6 @@ function getProfiler (target) {
} }
return deferred.promise; return deferred.promise;
} }
exports.getProfiler = Task.async(getProfiler);
/** /**
* Makes a request to an actor that does not have the modern `Front` * Makes a request to an actor that does not have the modern `Front`
@ -176,4 +169,10 @@ function actorCompatibilityBridge (method) {
} }
}; };
} }
exports.MockMemoryFront = MockMemoryFront;
exports.MockTimelineFront = MockTimelineFront;
exports.memoryActorSupported = memoryActorSupported;
exports.timelineActorSupported = timelineActorSupported;
exports.getProfiler = getProfiler;
exports.actorCompatibilityBridge = actorCompatibilityBridge; exports.actorCompatibilityBridge = actorCompatibilityBridge;

View File

@ -42,7 +42,7 @@ const gInflatedFrameStore = new WeakMap();
* Parses the raw location of this function call to retrieve the actual * Parses the raw location of this function call to retrieve the actual
* function name, source url, host name, line and column. * function name, source url, host name, line and column.
*/ */
exports.parseLocation = function parseLocation(location, fallbackLine, fallbackColumn) { function parseLocation(location, fallbackLine, fallbackColumn) {
// Parse the `location` for the function name, source url, line, column etc. // Parse the `location` for the function name, source url, line, column etc.
let line, column, url; let line, column, url;
@ -190,7 +190,6 @@ function isContent({ location, category }) {
// If there was no left parenthesis, try matching from the start. // If there was no left parenthesis, try matching from the start.
return isContentScheme(location, 0); return isContentScheme(location, 0);
} }
exports.isContent = isContent;
/** /**
* Get caches to cache inflated frames and computed frame keys of a frame * Get caches to cache inflated frames and computed frame keys of a frame
@ -199,7 +198,7 @@ exports.isContent = isContent;
* @param object framesTable * @param object framesTable
* @return object * @return object
*/ */
exports.getInflatedFrameCache = function getInflatedFrameCache(frameTable) { function getInflatedFrameCache(frameTable) {
let inflatedCache = gInflatedFrameStore.get(frameTable); let inflatedCache = gInflatedFrameStore.get(frameTable);
if (inflatedCache !== undefined) { if (inflatedCache !== undefined) {
return inflatedCache; return inflatedCache;
@ -220,7 +219,7 @@ exports.getInflatedFrameCache = function getInflatedFrameCache(frameTable) {
* @param object stringTable * @param object stringTable
* @param object allocationsTable * @param object allocationsTable
*/ */
exports.getOrAddInflatedFrame = function getOrAddInflatedFrame(cache, index, frameTable, stringTable, allocationsTable) { function getOrAddInflatedFrame(cache, index, frameTable, stringTable, allocationsTable) {
let inflatedFrame = cache[index]; let inflatedFrame = cache[index];
if (inflatedFrame === null) { if (inflatedFrame === null) {
inflatedFrame = cache[index] = new InflatedFrame(index, frameTable, stringTable, allocationsTable); inflatedFrame = cache[index] = new InflatedFrame(index, frameTable, stringTable, allocationsTable);
@ -292,8 +291,6 @@ InflatedFrame.prototype.getFrameKey = function getFrameKey(options) {
return ""; return "";
}; };
exports.InflatedFrame = InflatedFrame;
/** /**
* Helper for getting an nsIURL instance out of a string. * Helper for getting an nsIURL instance out of a string.
*/ */
@ -420,3 +417,9 @@ function isChromeScheme(location, i) {
function isNumeric(c) { function isNumeric(c) {
return c >= CHAR_CODE_0 && c <= CHAR_CODE_9; return c >= CHAR_CODE_0 && c <= CHAR_CODE_9;
} }
exports.parseLocation = parseLocation;
exports.isContent = isContent;
exports.getInflatedFrameCache = getInflatedFrameCache;
exports.getOrAddInflatedFrame = getOrAddInflatedFrame;
exports.InflatedFrame = InflatedFrame;

View File

@ -534,5 +534,5 @@ function getRecordingModelPrefs () {
}; };
} }
exports.getPerformanceActorsConnection = target => SharedPerformanceActors.forTarget(target); exports.getPerformanceActorsConnection = t => SharedPerformanceActors.forTarget(t);
exports.PerformanceFront = PerformanceFront; exports.PerformanceFront = PerformanceFront;

View File

@ -8,7 +8,7 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
loader.lazyRequireGetter(this, "Services"); loader.lazyRequireGetter(this, "Services");
loader.lazyRequireGetter(this, "promise"); loader.lazyRequireGetter(this, "promise");
loader.lazyRequireGetter(this, "RecordingUtils", loader.lazyRequireGetter(this, "RecordingUtils",
"devtools/performance/recording-utils", true); "devtools/performance/recording-utils");
loader.lazyImporter(this, "FileUtils", loader.lazyImporter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm"); "resource://gre/modules/FileUtils.jsm");
@ -111,8 +111,6 @@ let PerformanceIO = {
} }
}; };
exports.PerformanceIO = PerformanceIO;
/** /**
* Returns a boolean indicating whether or not the passed in `version` * Returns a boolean indicating whether or not the passed in `version`
* is supported by this serializer. * is supported by this serializer.
@ -161,3 +159,5 @@ function convertLegacyData (legacyData) {
return data; return data;
} }
exports.PerformanceIO = PerformanceIO;

View File

@ -114,7 +114,7 @@ const SUCCESSFUL_OUTCOMES = [
* @type {number} id * @type {number} id
*/ */
const OptimizationSite = exports.OptimizationSite = function (id, opts) { const OptimizationSite = function (id, opts) {
this.id = id; this.id = id;
this.data = opts; this.data = opts;
this.samples = 1; this.samples = 1;
@ -166,7 +166,7 @@ OptimizationSite.prototype.getIonTypes = function () {
* JIT optimizations. Do not modify this! * JIT optimizations. Do not modify this!
*/ */
const JITOptimizations = exports.JITOptimizations = function (rawSites, stringTable) { const JITOptimizations = function (rawSites, stringTable) {
// Build a histogram of optimization sites. // Build a histogram of optimization sites.
let sites = []; let sites = [];
@ -238,3 +238,6 @@ function maybeTypeset(typeset, stringTable) {
}; };
}); });
} }
exports.OptimizationSite = OptimizationSite;
exports.JITOptimizations = JITOptimizations;

View File

@ -28,7 +28,6 @@ function getMarkerLabel (marker) {
// as a string. // as a string.
return typeof blueprint.label === "function" ? blueprint.label(marker) : blueprint.label; return typeof blueprint.label === "function" ? blueprint.label(marker) : blueprint.label;
} }
exports.getMarkerLabel = getMarkerLabel;
/** /**
* Returns the correct generic name for a marker class, like "Function Call" * Returns the correct generic name for a marker class, like "Function Call"
@ -57,7 +56,6 @@ function getMarkerClassName (type) {
return className; return className;
} }
exports.getMarkerClassName = getMarkerClassName;
/** /**
* Returns an array of objects with key/value pairs of what should be rendered * Returns an array of objects with key/value pairs of what should be rendered
@ -92,12 +90,11 @@ function getMarkerFields (marker) {
return fields; return fields;
}, []); }, []);
} }
exports.getMarkerFields = getMarkerFields;
/** /**
* Utilites for creating elements for markers. * Utilites for creating elements for markers.
*/ */
const DOM = exports.DOM = { const DOM = {
/** /**
* Builds all the fields possible for the given marker. Returns an * Builds all the fields possible for the given marker. Returns an
* array of elements to be appended to a parent element. * array of elements to be appended to a parent element.
@ -272,3 +269,8 @@ const DOM = exports.DOM = {
return container; return container;
} }
}; };
exports.getMarkerLabel = getMarkerLabel;
exports.getMarkerClassName = getMarkerClassName;
exports.getMarkerFields = getMarkerFields;
exports.DOM = DOM;

View File

@ -9,7 +9,7 @@ const { Task } = require("resource://gre/modules/Task.jsm");
loader.lazyRequireGetter(this, "PerformanceIO", loader.lazyRequireGetter(this, "PerformanceIO",
"devtools/performance/io", true); "devtools/performance/io", true);
loader.lazyRequireGetter(this, "RecordingUtils", loader.lazyRequireGetter(this, "RecordingUtils",
"devtools/performance/recording-utils", true); "devtools/performance/recording-utils");
/** /**
* Model for a wholistic profile, containing the duration, profiling data, * Model for a wholistic profile, containing the duration, profiling data,

View File

@ -10,8 +10,6 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
* such as filtering profile samples or offsetting timestamps. * such as filtering profile samples or offsetting timestamps.
*/ */
exports.RecordingUtils = {};
/** /**
* Filters all the samples in the provided profiler data to be more recent * Filters all the samples in the provided profiler data to be more recent
* than the specified start time. * than the specified start time.
@ -21,7 +19,7 @@ exports.RecordingUtils = {};
* @param number profilerStartTime * @param number profilerStartTime
* The earliest acceptable sample time (in milliseconds). * The earliest acceptable sample time (in milliseconds).
*/ */
exports.RecordingUtils.filterSamples = function(profile, profilerStartTime) { function filterSamples(profile, profilerStartTime) {
let firstThread = profile.threads[0]; let firstThread = profile.threads[0];
const TIME_SLOT = firstThread.samples.schema.time; const TIME_SLOT = firstThread.samples.schema.time;
firstThread.samples.data = firstThread.samples.data.filter(e => { firstThread.samples.data = firstThread.samples.data.filter(e => {
@ -37,7 +35,7 @@ exports.RecordingUtils.filterSamples = function(profile, profilerStartTime) {
* @param number timeOffset * @param number timeOffset
* The amount of time to offset by (in milliseconds). * The amount of time to offset by (in milliseconds).
*/ */
exports.RecordingUtils.offsetSampleTimes = function(profile, timeOffset) { function offsetSampleTimes(profile, timeOffset) {
let firstThread = profile.threads[0]; let firstThread = profile.threads[0];
const TIME_SLOT = firstThread.samples.schema.time; const TIME_SLOT = firstThread.samples.schema.time;
let samplesData = firstThread.samples.data; let samplesData = firstThread.samples.data;
@ -54,7 +52,7 @@ exports.RecordingUtils.offsetSampleTimes = function(profile, timeOffset) {
* @param number timeOffset * @param number timeOffset
* The amount of time to offset by (in milliseconds). * The amount of time to offset by (in milliseconds).
*/ */
exports.RecordingUtils.offsetMarkerTimes = function(markers, timeOffset) { function offsetMarkerTimes(markers, timeOffset) {
for (let marker of markers) { for (let marker of markers) {
marker.start -= timeOffset; marker.start -= timeOffset;
marker.end -= timeOffset; marker.end -= timeOffset;
@ -72,7 +70,7 @@ exports.RecordingUtils.offsetMarkerTimes = function(markers, timeOffset) {
* @param number timeScale * @param number timeScale
* The factor to scale by, after offsetting. * The factor to scale by, after offsetting.
*/ */
exports.RecordingUtils.offsetAndScaleTimestamps = function(timestamps, timeOffset, timeScale) { function offsetAndScaleTimestamps(timestamps, timeOffset, timeScale) {
for (let i = 0, len = timestamps.length; i < len; i++) { for (let i = 0, len = timestamps.length; i < len; i++) {
timestamps[i] -= timeOffset; timestamps[i] -= timeOffset;
timestamps[i] /= timeScale; timestamps[i] /= timeScale;
@ -95,7 +93,7 @@ let gProfileThreadFromAllocationCache = new WeakMap();
* @return object * @return object
* The "profile" describing the allocations log. * The "profile" describing the allocations log.
*/ */
exports.RecordingUtils.getProfileThreadFromAllocations = function(allocations) { function getProfileThreadFromAllocations(allocations) {
let cached = gProfileThreadFromAllocationCache.get(allocations); let cached = gProfileThreadFromAllocationCache.get(allocations);
if (cached) { if (cached) {
return cached; return cached;
@ -208,7 +206,7 @@ exports.RecordingUtils.getProfileThreadFromAllocations = function(allocations) {
* @return object * @return object
* The filtered timeline blueprint. * The filtered timeline blueprint.
*/ */
exports.RecordingUtils.getFilteredBlueprint = function({ blueprint, hiddenMarkers }) { function getFilteredBlueprint({ blueprint, hiddenMarkers }) {
// Clone functions here just to prevent an error, as the blueprint // Clone functions here just to prevent an error, as the blueprint
// contains functions (even though we do not use them). // contains functions (even though we do not use them).
let filteredBlueprint = Cu.cloneInto(blueprint, {}, { cloneFunctions: true }); let filteredBlueprint = Cu.cloneInto(blueprint, {}, { cloneFunctions: true });
@ -259,7 +257,7 @@ exports.RecordingUtils.getFilteredBlueprint = function({ blueprint, hiddenMarker
* @param object profile * @param object profile
* A profile with version 2. * A profile with version 2.
*/ */
exports.RecordingUtils.deflateProfile = function deflateProfile(profile) { function deflateProfile(profile) {
profile.threads = profile.threads.map((thread) => { profile.threads = profile.threads.map((thread) => {
let uniqueStacks = new UniqueStacks(); let uniqueStacks = new UniqueStacks();
return deflateThread(thread, uniqueStacks); return deflateThread(thread, uniqueStacks);
@ -379,7 +377,6 @@ function deflateThread(thread, uniqueStacks) {
stringTable: uniqueStacks.getStringTable() stringTable: uniqueStacks.getStringTable()
}; };
} }
exports.RecordingUtils.deflateThread = deflateThread;
function stackTableWithSchema(data) { function stackTableWithSchema(data) {
let slot = 0; let slot = 0;
@ -448,8 +445,6 @@ UniqueStrings.prototype.getOrAddStringIndex = function(s) {
return index; return index;
}; };
exports.RecordingUtils.UniqueStrings = UniqueStrings;
/** /**
* A helper class to deduplicate old-version profiles. * A helper class to deduplicate old-version profiles.
* *
@ -571,4 +566,13 @@ UniqueStacks.prototype.getOrAddStringIndex = function(s) {
return this._uniqueStrings.getOrAddStringIndex(s); return this._uniqueStrings.getOrAddStringIndex(s);
}; };
exports.RecordingUtils.UniqueStacks = UniqueStacks; exports.filterSamples = filterSamples;
exports.offsetSampleTimes = offsetSampleTimes;
exports.offsetMarkerTimes = offsetMarkerTimes;
exports.offsetAndScaleTimestamps = offsetAndScaleTimestamps;
exports.getProfileThreadFromAllocations = getProfileThreadFromAllocations;
exports.getFilteredBlueprint = getFilteredBlueprint;
exports.deflateProfile = deflateProfile;
exports.deflateThread = deflateThread;
exports.UniqueStrings = UniqueStrings;
exports.UniqueStacks = UniqueStacks;

View File

@ -20,10 +20,6 @@ loader.lazyRequireGetter(this, "JITOptimizations",
loader.lazyRequireGetter(this, "FrameUtils", loader.lazyRequireGetter(this, "FrameUtils",
"devtools/performance/frame-utils"); "devtools/performance/frame-utils");
exports.ThreadNode = ThreadNode;
exports.FrameNode = FrameNode;
exports.FrameNode.isContent = FrameUtils.isContent;
/** /**
* A call tree for a thread. This is essentially a linkage between all frames * A call tree for a thread. This is essentially a linkage between all frames
* of all samples into a single tree structure, with additional information * of all samples into a single tree structure, with additional information
@ -502,3 +498,6 @@ FrameNode.prototype = {
return new JITOptimizations(this._optimizations, this._stringTable); return new JITOptimizations(this._optimizations, this._stringTable);
} }
}; };
exports.ThreadNode = ThreadNode;
exports.FrameNode = FrameNode;

View File

@ -100,8 +100,6 @@ MarkerDetails.prototype = {
}, },
}; };
exports.MarkerDetails = MarkerDetails;
/** /**
* Take an element from an event `target`, and asend through * Take an element from an event `target`, and asend through
* the DOM, looking for an element with a `data-action` attribute. Return * the DOM, looking for an element with a `data-action` attribute. Return
@ -123,3 +121,5 @@ function findActionFromEvent (target, container) {
} }
return null; return null;
} }
exports.MarkerDetails = MarkerDetails;

View File

@ -41,8 +41,6 @@ const DEFAULT_VISIBLE_CELLS = {
const clamp = (val, min, max) => Math.max(min, Math.min(max, val)); const clamp = (val, min, max) => Math.max(min, Math.min(max, val));
const sum = vals => vals.reduce((a, b) => a + b, 0); const sum = vals => vals.reduce((a, b) => a + b, 0);
exports.CallView = CallView;
/** /**
* An item in a call tree view, which looks like this: * An item in a call tree view, which looks like this:
* *
@ -123,34 +121,32 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
* @return nsIDOMNode * @return nsIDOMNode
*/ */
_displaySelf: function(document, arrowNode) { _displaySelf: function(document, arrowNode) {
this.document = document;
let displayedData = this.getDisplayedData(); let displayedData = this.getDisplayedData();
let frameInfo = this.frame.getInfo(); let frameInfo = this.frame.getInfo();
if (this.visibleCells.duration) { if (this.visibleCells.duration) {
var durationCell = this._createTimeCell(displayedData.totalDuration); var durationCell = this._createTimeCell(document, displayedData.totalDuration);
} }
if (this.visibleCells.selfDuration) { if (this.visibleCells.selfDuration) {
var selfDurationCell = this._createTimeCell(displayedData.selfDuration, true); var selfDurationCell = this._createTimeCell(document, displayedData.selfDuration, true);
} }
if (this.visibleCells.percentage) { if (this.visibleCells.percentage) {
var percentageCell = this._createExecutionCell(displayedData.totalPercentage); var percentageCell = this._createExecutionCell(document, displayedData.totalPercentage);
} }
if (this.visibleCells.selfPercentage) { if (this.visibleCells.selfPercentage) {
var selfPercentageCell = this._createExecutionCell(displayedData.selfPercentage, true); var selfPercentageCell = this._createExecutionCell(document, displayedData.selfPercentage, true);
} }
if (this.visibleCells.allocations) { if (this.visibleCells.allocations) {
var allocationsCell = this._createAllocationsCell(displayedData.totalAllocations); var allocationsCell = this._createAllocationsCell(document, displayedData.totalAllocations);
} }
if (this.visibleCells.selfAllocations) { if (this.visibleCells.selfAllocations) {
var selfAllocationsCell = this._createAllocationsCell(displayedData.selfAllocations, true); var selfAllocationsCell = this._createAllocationsCell(document, displayedData.selfAllocations, true);
} }
if (this.visibleCells.samples) { if (this.visibleCells.samples) {
var samplesCell = this._createSamplesCell(displayedData.samples); var samplesCell = this._createSamplesCell(document, displayedData.samples);
} }
if (this.visibleCells.function) { if (this.visibleCells.function) {
var functionCell = this._createFunctionCell(arrowNode, displayedData.name, frameInfo, this.level); var functionCell = this._createFunctionCell(document, arrowNode, displayedData.name, frameInfo, this.level);
} }
let targetNode = document.createElement("hbox"); let targetNode = document.createElement("hbox");
@ -216,40 +212,40 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
* Functions creating each cell in this call view. * Functions creating each cell in this call view.
* Invoked by `_displaySelf`. * Invoked by `_displaySelf`.
*/ */
_createTimeCell: function(duration, isSelf = false) { _createTimeCell: function(doc, duration, isSelf = false) {
let cell = this.document.createElement("label"); let cell = doc.createElement("label");
cell.className = "plain call-tree-cell"; cell.className = "plain call-tree-cell";
cell.setAttribute("type", isSelf ? "self-duration" : "duration"); cell.setAttribute("type", isSelf ? "self-duration" : "duration");
cell.setAttribute("crop", "end"); cell.setAttribute("crop", "end");
cell.setAttribute("value", L10N.numberWithDecimals(duration, 2) + " " + MILLISECOND_UNITS); cell.setAttribute("value", L10N.numberWithDecimals(duration, 2) + " " + MILLISECOND_UNITS);
return cell; return cell;
}, },
_createExecutionCell: function(percentage, isSelf = false) { _createExecutionCell: function(doc, percentage, isSelf = false) {
let cell = this.document.createElement("label"); let cell = doc.createElement("label");
cell.className = "plain call-tree-cell"; cell.className = "plain call-tree-cell";
cell.setAttribute("type", isSelf ? "self-percentage" : "percentage"); cell.setAttribute("type", isSelf ? "self-percentage" : "percentage");
cell.setAttribute("crop", "end"); cell.setAttribute("crop", "end");
cell.setAttribute("value", L10N.numberWithDecimals(percentage, 2) + PERCENTAGE_UNITS); cell.setAttribute("value", L10N.numberWithDecimals(percentage, 2) + PERCENTAGE_UNITS);
return cell; return cell;
}, },
_createAllocationsCell: function(count, isSelf = false) { _createAllocationsCell: function(doc, count, isSelf = false) {
let cell = this.document.createElement("label"); let cell = doc.createElement("label");
cell.className = "plain call-tree-cell"; cell.className = "plain call-tree-cell";
cell.setAttribute("type", isSelf ? "self-allocations" : "allocations"); cell.setAttribute("type", isSelf ? "self-allocations" : "allocations");
cell.setAttribute("crop", "end"); cell.setAttribute("crop", "end");
cell.setAttribute("value", count || 0); cell.setAttribute("value", count || 0);
return cell; return cell;
}, },
_createSamplesCell: function(count) { _createSamplesCell: function(doc, count) {
let cell = this.document.createElement("label"); let cell = doc.createElement("label");
cell.className = "plain call-tree-cell"; cell.className = "plain call-tree-cell";
cell.setAttribute("type", "samples"); cell.setAttribute("type", "samples");
cell.setAttribute("crop", "end"); cell.setAttribute("crop", "end");
cell.setAttribute("value", count || ""); cell.setAttribute("value", count || "");
return cell; return cell;
}, },
_createFunctionCell: function(arrowNode, frameName, frameInfo, frameLevel) { _createFunctionCell: function(doc, arrowNode, frameName, frameInfo, frameLevel) {
let cell = this.document.createElement("hbox"); let cell = doc.createElement("hbox");
cell.className = "call-tree-cell"; cell.className = "call-tree-cell";
cell.style.MozMarginStart = (frameLevel * CALL_TREE_INDENTATION) + "px"; cell.style.MozMarginStart = (frameLevel * CALL_TREE_INDENTATION) + "px";
cell.setAttribute("type", "function"); cell.setAttribute("type", "function");
@ -258,7 +254,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
// Don't render a name label node if there's no function name. A different // Don't render a name label node if there's no function name. A different
// location label node will be rendered instead. // location label node will be rendered instead.
if (frameName) { if (frameName) {
let nameNode = this.document.createElement("label"); let nameNode = doc.createElement("label");
nameNode.className = "plain call-tree-name"; nameNode.className = "plain call-tree-name";
nameNode.setAttribute("flex", "1"); nameNode.setAttribute("flex", "1");
nameNode.setAttribute("crop", "end"); nameNode.setAttribute("crop", "end");
@ -268,7 +264,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
// Don't render detailed labels for meta category frames // Don't render detailed labels for meta category frames
if (!frameInfo.isMetaCategory) { if (!frameInfo.isMetaCategory) {
this._appendFunctionDetailsCells(cell, frameInfo); this._appendFunctionDetailsCells(doc, cell, frameInfo);
} }
// Don't render an expando-arrow for leaf nodes. // Don't render an expando-arrow for leaf nodes.
@ -279,9 +275,9 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
return cell; return cell;
}, },
_appendFunctionDetailsCells: function(cell, frameInfo) { _appendFunctionDetailsCells: function(doc, cell, frameInfo) {
if (frameInfo.fileName) { if (frameInfo.fileName) {
let urlNode = this.document.createElement("label"); let urlNode = doc.createElement("label");
urlNode.className = "plain call-tree-url"; urlNode.className = "plain call-tree-url";
urlNode.setAttribute("flex", "1"); urlNode.setAttribute("flex", "1");
urlNode.setAttribute("crop", "end"); urlNode.setAttribute("crop", "end");
@ -292,32 +288,32 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
} }
if (frameInfo.line) { if (frameInfo.line) {
let lineNode = this.document.createElement("label"); let lineNode = doc.createElement("label");
lineNode.className = "plain call-tree-line"; lineNode.className = "plain call-tree-line";
lineNode.setAttribute("value", ":" + frameInfo.line); lineNode.setAttribute("value", ":" + frameInfo.line);
cell.appendChild(lineNode); cell.appendChild(lineNode);
} }
if (frameInfo.column) { if (frameInfo.column) {
let columnNode = this.document.createElement("label"); let columnNode = doc.createElement("label");
columnNode.className = "plain call-tree-column"; columnNode.className = "plain call-tree-column";
columnNode.setAttribute("value", ":" + frameInfo.column); columnNode.setAttribute("value", ":" + frameInfo.column);
cell.appendChild(columnNode); cell.appendChild(columnNode);
} }
if (frameInfo.host) { if (frameInfo.host) {
let hostNode = this.document.createElement("label"); let hostNode = doc.createElement("label");
hostNode.className = "plain call-tree-host"; hostNode.className = "plain call-tree-host";
hostNode.setAttribute("value", frameInfo.host); hostNode.setAttribute("value", frameInfo.host);
cell.appendChild(hostNode); cell.appendChild(hostNode);
} }
let spacerNode = this.document.createElement("spacer"); let spacerNode = doc.createElement("spacer");
spacerNode.setAttribute("flex", "10000"); spacerNode.setAttribute("flex", "10000");
cell.appendChild(spacerNode); cell.appendChild(spacerNode);
if (frameInfo.categoryData.label) { if (frameInfo.categoryData.label) {
let categoryNode = this.document.createElement("label"); let categoryNode = doc.createElement("label");
categoryNode.className = "plain call-tree-category"; categoryNode.className = "plain call-tree-category";
categoryNode.style.color = frameInfo.categoryData.color; categoryNode.style.color = frameInfo.categoryData.color;
categoryNode.setAttribute("value", frameInfo.categoryData.label); categoryNode.setAttribute("value", frameInfo.categoryData.label);
@ -402,3 +398,5 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
this.root.emit("link", this); this.root.emit("link", this);
} }
}); });
exports.CallView = CallView;

View File

@ -20,7 +20,7 @@ loader.lazyRequireGetter(this, "L10N",
loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT", loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
"devtools/performance/global", true); "devtools/performance/global", true);
loader.lazyRequireGetter(this, "RecordingUtils", loader.lazyRequireGetter(this, "RecordingUtils",
"devtools/performance/recording-utils", true); "devtools/performance/recording-utils");
loader.lazyRequireGetter(this, "RecordingModel", loader.lazyRequireGetter(this, "RecordingModel",
"devtools/performance/recording-model", true); "devtools/performance/recording-model", true);
loader.lazyRequireGetter(this, "GraphsController", loader.lazyRequireGetter(this, "GraphsController",

View File

@ -8,7 +8,7 @@
*/ */
function test() { function test() {
let { RecordingUtils } = devtools.require("devtools/performance/recording-utils"); let RecordingUtils = devtools.require("devtools/performance/recording-utils");
let output = RecordingUtils.getProfileThreadFromAllocations(TEST_DATA); let output = RecordingUtils.getProfileThreadFromAllocations(TEST_DATA);
is(output.toSource(), EXPECTED_OUTPUT.toSource(), "The output is correct."); is(output.toSource(), EXPECTED_OUTPUT.toSource(), "The output is correct.");

View File

@ -5,7 +5,8 @@
* Tests that the call tree up/down events work for js calltree and memory calltree. * Tests that the call tree up/down events work for js calltree and memory calltree.
*/ */
const { ThreadNode } = devtools.require("devtools/performance/tree-model"); const { ThreadNode } = devtools.require("devtools/performance/tree-model");
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils") const RecordingUtils = devtools.require("devtools/performance/recording-utils")
function spawnTest () { function spawnTest () {
let focus = 0; let focus = 0;
let focusEvent = () => focus++; let focusEvent = () => focus++;

View File

@ -7,7 +7,7 @@
* FrameNode, and the returning of that data is as expected. * FrameNode, and the returning of that data is as expected.
*/ */
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils"); const RecordingUtils = devtools.require("devtools/performance/recording-utils");
function test() { function test() {
let { JITOptimizations } = devtools.require("devtools/performance/jit"); let { JITOptimizations } = devtools.require("devtools/performance/jit");

View File

@ -6,7 +6,7 @@
* OptimizationSites methods work as expected. * OptimizationSites methods work as expected.
*/ */
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils"); const RecordingUtils = devtools.require("devtools/performance/recording-utils");
function test() { function test() {
let { JITOptimizations, OptimizationSite } = devtools.require("devtools/performance/jit"); let { JITOptimizations, OptimizationSite } = devtools.require("devtools/performance/jit");

View File

@ -6,7 +6,7 @@
* if on, and displays selected frames on focus. * if on, and displays selected frames on focus.
*/ */
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils"); const RecordingUtils = devtools.require("devtools/performance/recording-utils");
Services.prefs.setBoolPref(INVERT_PREF, false); Services.prefs.setBoolPref(INVERT_PREF, false);

View File

@ -7,7 +7,7 @@
*/ */
const { CATEGORY_MASK } = devtools.require("devtools/performance/global"); const { CATEGORY_MASK } = devtools.require("devtools/performance/global");
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils"); const RecordingUtils = devtools.require("devtools/performance/recording-utils");
Services.prefs.setBoolPref(INVERT_PREF, false); Services.prefs.setBoolPref(INVERT_PREF, false);
Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false); Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);

View File

@ -5,7 +5,7 @@
* Tests if the performance tool can import profiler data from the * Tests if the performance tool can import profiler data from the
* original profiler tool (Performance Recording v1, and Profiler data v2) and the correct views and graphs are loaded. * original profiler tool (Performance Recording v1, and Profiler data v2) and the correct views and graphs are loaded.
*/ */
let { RecordingUtils } = devtools.require("devtools/performance/recording-utils"); let RecordingUtils = devtools.require("devtools/performance/recording-utils");
let TICKS_DATA = (function () { let TICKS_DATA = (function () {
let ticks = []; let ticks = [];

View File

@ -7,44 +7,44 @@
*/ */
function test() { function test() {
let { FrameNode } = devtools.require("devtools/performance/tree-model"); let FrameUtils = devtools.require("devtools/performance/frame-utils");
ok(FrameNode.isContent({ location: "http://foo" }), ok(FrameUtils.isContent({ location: "http://foo" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(FrameNode.isContent({ location: "https://foo" }), ok(FrameUtils.isContent({ location: "https://foo" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(FrameNode.isContent({ location: "file://foo" }), ok(FrameUtils.isContent({ location: "file://foo" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ location: "chrome://foo" }), ok(!FrameUtils.isContent({ location: "chrome://foo" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ location: "resource://foo" }), ok(!FrameUtils.isContent({ location: "resource://foo" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ location: "chrome://foo -> http://bar" }), ok(!FrameUtils.isContent({ location: "chrome://foo -> http://bar" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ location: "chrome://foo -> https://bar" }), ok(!FrameUtils.isContent({ location: "chrome://foo -> https://bar" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ location: "chrome://foo -> file://bar" }), ok(!FrameUtils.isContent({ location: "chrome://foo -> file://bar" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ location: "resource://foo -> http://bar" }), ok(!FrameUtils.isContent({ location: "resource://foo -> http://bar" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ location: "resource://foo -> https://bar" }), ok(!FrameUtils.isContent({ location: "resource://foo -> https://bar" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ location: "resource://foo -> file://bar" }), ok(!FrameUtils.isContent({ location: "resource://foo -> file://bar" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ category: 1, location: "chrome://foo" }), ok(!FrameUtils.isContent({ category: 1, location: "chrome://foo" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ category: 1, location: "resource://foo" }), ok(!FrameUtils.isContent({ category: 1, location: "resource://foo" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ category: 1, location: "file://foo -> http://bar" }), ok(!FrameUtils.isContent({ category: 1, location: "file://foo -> http://bar" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ category: 1, location: "file://foo -> https://bar" }), ok(!FrameUtils.isContent({ category: 1, location: "file://foo -> https://bar" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
ok(!FrameNode.isContent({ category: 1, location: "file://foo -> file://bar" }), ok(!FrameUtils.isContent({ category: 1, location: "file://foo -> file://bar" }),
"Verifying content/chrome frames is working properly."); "Verifying content/chrome frames is working properly.");
finish(); finish();

View File

@ -6,13 +6,14 @@
*/ */
function test() { function test() {
let FrameUtils = devtools.require("devtools/performance/frame-utils");
let { FrameNode } = devtools.require("devtools/performance/tree-model"); let { FrameNode } = devtools.require("devtools/performance/tree-model");
let { CATEGORY_OTHER } = devtools.require("devtools/performance/global"); let { CATEGORY_OTHER } = devtools.require("devtools/performance/global");
let frame1 = new FrameNode("hello/<.world (http://foo/bar.js:123:987)", { let frame1 = new FrameNode("hello/<.world (http://foo/bar.js:123:987)", {
location: "hello/<.world (http://foo/bar.js:123:987)", location: "hello/<.world (http://foo/bar.js:123:987)",
line: 456, line: 456,
isContent: FrameNode.isContent({ isContent: FrameUtils.isContent({
location: "hello/<.world (http://foo/bar.js:123:987)" location: "hello/<.world (http://foo/bar.js:123:987)"
}) })
}, false); }, false);
@ -39,7 +40,7 @@ function test() {
let frame2 = new FrameNode("hello/<.world (http://foo/bar.js#baz:123:987)", { let frame2 = new FrameNode("hello/<.world (http://foo/bar.js#baz:123:987)", {
location: "hello/<.world (http://foo/bar.js#baz:123:987)", location: "hello/<.world (http://foo/bar.js#baz:123:987)",
line: 456, line: 456,
isContent: FrameNode.isContent({ isContent: FrameUtils.isContent({
location: "hello/<.world (http://foo/bar.js#baz:123:987)" location: "hello/<.world (http://foo/bar.js#baz:123:987)"
}) })
}, false); }, false);
@ -66,7 +67,7 @@ function test() {
let frame3 = new FrameNode("hello/<.world (http://foo/#bar:123:987)", { let frame3 = new FrameNode("hello/<.world (http://foo/#bar:123:987)", {
location: "hello/<.world (http://foo/#bar:123:987)", location: "hello/<.world (http://foo/#bar:123:987)",
line: 456, line: 456,
isContent: FrameNode.isContent({ isContent: FrameUtils.isContent({
location: "hello/<.world (http://foo/#bar:123:987)" location: "hello/<.world (http://foo/#bar:123:987)"
}) })
}, false); }, false);
@ -93,7 +94,7 @@ function test() {
let frame4 = new FrameNode("hello/<.world (http://foo/:123:987)", { let frame4 = new FrameNode("hello/<.world (http://foo/:123:987)", {
location: "hello/<.world (http://foo/:123:987)", location: "hello/<.world (http://foo/:123:987)",
line: 456, line: 456,
isContent: FrameNode.isContent({ isContent: FrameUtils.isContent({
location: "hello/<.world (http://foo/:123:987)" location: "hello/<.world (http://foo/:123:987)"
}) })
}, false); }, false);
@ -120,7 +121,7 @@ function test() {
let frame5 = new FrameNode("hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", { let frame5 = new FrameNode("hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", {
location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)",
line: 456, line: 456,
isContent: FrameNode.isContent({ isContent: FrameUtils.isContent({
location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)" location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)"
}) })
}, false); }, false);
@ -148,7 +149,7 @@ function test() {
location: "Foo::Bar::Baz", location: "Foo::Bar::Baz",
line: 456, line: 456,
category: CATEGORY_OTHER, category: CATEGORY_OTHER,
isContent: FrameNode.isContent({ isContent: FrameUtils.isContent({
location: "Foo::Bar::Baz", location: "Foo::Bar::Baz",
category: CATEGORY_OTHER category: CATEGORY_OTHER
}) })
@ -173,7 +174,7 @@ function test() {
let frame7 = new FrameNode("EnterJIT", { let frame7 = new FrameNode("EnterJIT", {
location: "EnterJIT", location: "EnterJIT",
isContent: FrameNode.isContent({ isContent: FrameUtils.isContent({
location: "EnterJIT" location: "EnterJIT"
}) })
}, false); }, false);
@ -217,7 +218,7 @@ function test() {
let frame10 = new FrameNode("main (http://localhost:8888/file.js:123:987)", { let frame10 = new FrameNode("main (http://localhost:8888/file.js:123:987)", {
location: "main (http://localhost:8888/file.js:123:987)", location: "main (http://localhost:8888/file.js:123:987)",
line: 123, line: 123,
isContent: FrameNode.isContent({ isContent: FrameUtils.isContent({
location: "main (http://localhost:8888/file.js:123:987)" location: "main (http://localhost:8888/file.js:123:987)"
}) })
}, false); }, false);

View File

@ -6,7 +6,7 @@
* the FrameNodes have the correct optimization data after iterating over samples. * the FrameNodes have the correct optimization data after iterating over samples.
*/ */
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils"); const RecordingUtils = devtools.require("devtools/performance/recording-utils");
let gUniqueStacks = new RecordingUtils.UniqueStacks(); let gUniqueStacks = new RecordingUtils.UniqueStacks();

View File

@ -557,7 +557,7 @@ function getFrameNodePath(root, path) {
* Synthesize a profile for testing. * Synthesize a profile for testing.
*/ */
function synthesizeProfileForTest(samples) { function synthesizeProfileForTest(samples) {
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils"); const RecordingUtils = devtools.require("devtools/performance/recording-utils");
samples.unshift({ samples.unshift({
time: 0, time: 0,

View File

@ -21,6 +21,8 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
DetailsSubview.initialize.call(this); DetailsSubview.initialize.call(this);
this._onLink = this._onLink.bind(this); this._onLink = this._onLink.bind(this);
this.container = $("#memory-calltree-view > .call-tree-cells-container");
}, },
/** /**
@ -35,10 +37,11 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
* *
* @param object interval [optional] * @param object interval [optional]
* The { startTime, endTime }, in milliseconds. * The { startTime, endTime }, in milliseconds.
* @param object options [optional]
* Additional options for new the call tree.
*/ */
render: function (interval={}, options={}) { render: function (interval={}) {
let options = {
invertTree: PerformanceController.getOption("invert-call-tree")
};
let recording = PerformanceController.getCurrentRecording(); let recording = PerformanceController.getCurrentRecording();
let allocations = recording.getAllocations(); let allocations = recording.getAllocations();
let threadNode = this._prepareCallTree(allocations, interval, options); let threadNode = this._prepareCallTree(allocations, interval, options);
@ -66,33 +69,30 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
*/ */
_prepareCallTree: function (allocations, { startTime, endTime }, options) { _prepareCallTree: function (allocations, { startTime, endTime }, options) {
let thread = RecordingUtils.getProfileThreadFromAllocations(allocations); let thread = RecordingUtils.getProfileThreadFromAllocations(allocations);
let invertTree = PerformanceController.getOption("invert-call-tree"); let { invertTree } = options;
let threadNode = new ThreadNode(thread, return new ThreadNode(thread, { startTime, endTime, invertTree });
{ startTime, endTime, invertTree });
// If we have an empty profile (no samples), then don't invert the tree, as
// it would hide the root node and a completely blank call tree space can be
// mis-interpreted as an error.
options.inverted = invertTree && threadNode.samples > 0;
return threadNode;
}, },
/** /**
* Renders the call tree. * Renders the call tree.
*/ */
_populateCallTree: function (frameNode, options={}) { _populateCallTree: function (frameNode, options={}) {
// If we have an empty profile (no samples), then don't invert the tree, as
// it would hide the root node and a completely blank call tree space can be
// mis-interpreted as an error.
let inverted = options.invertTree && frameNode.samples > 0;
let root = new CallView({ let root = new CallView({
frame: frameNode, frame: frameNode,
inverted: options.inverted, inverted: inverted,
// Root nodes are hidden in inverted call trees. // Root nodes are hidden in inverted call trees.
hidden: options.inverted, hidden: inverted,
// Memory call trees should be sorted by allocations. // Memory call trees should be sorted by allocations.
sortingPredicate: (a, b) => a.frame.allocations < b.frame.allocations ? 1 : -1, sortingPredicate: (a, b) => a.frame.allocations < b.frame.allocations ? 1 : -1,
// Call trees should only auto-expand when not inverted. Passing undefined // Call trees should only auto-expand when not inverted. Passing undefined
// will default to the CALL_TREE_AUTO_EXPAND depth. // will default to the CALL_TREE_AUTO_EXPAND depth.
autoExpandDepth: options.inverted ? 0 : undefined, autoExpandDepth: inverted ? 0 : undefined,
// Some cells like the time duration and cost percentage don't make sense // Some cells like the time duration and cost percentage don't make sense
// for a memory allocations call tree. // for a memory allocations call tree.
visibleCells: { visibleCells: {
@ -109,9 +109,8 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
root.on("focus", () => this.emit("focus")); root.on("focus", () => this.emit("focus"));
// Clear out other call trees. // Clear out other call trees.
let container = $("#memory-calltree-view > .call-tree-cells-container"); this.container.innerHTML = "";
container.innerHTML = ""; root.attachTo(this.container);
root.attachTo(container);
// Memory allocation samples don't contain cateogry labels. // Memory allocation samples don't contain cateogry labels.
root.toggleCategories(false); root.toggleCategories(false);

View File

@ -250,7 +250,7 @@ function* openAndCloseToolbox(nbOfTimes, usageTime, toolId) {
* Synthesize a profile for testing. * Synthesize a profile for testing.
*/ */
function synthesizeProfileForTest(samples) { function synthesizeProfileForTest(samples) {
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils"); const RecordingUtils = devtools.require("devtools/performance/recording-utils");
samples.unshift({ samples.unshift({
time: 0, time: 0,

View File

@ -174,7 +174,9 @@ public:
mMethodString = aString; mMethodString = aString;
for (uint32_t i = 0; i < aArguments.Length(); ++i) { for (uint32_t i = 0; i < aArguments.Length(); ++i) {
mArguments.AppendElement(aArguments[i]); if (!mArguments.AppendElement(aArguments[i])) {
return;
}
} }
} }
@ -666,7 +668,9 @@ private:
return; return;
} }
arguments.AppendElement(value); if (!arguments.AppendElement(value)) {
return;
}
} }
mConsole->ProfileMethod(aCx, mAction, arguments); mConsole->ProfileMethod(aCx, mAction, arguments);
@ -826,8 +830,8 @@ Console::Time(JSContext* aCx, const JS::Handle<JS::Value> aTime)
Sequence<JS::Value> data; Sequence<JS::Value> data;
SequenceRooter<JS::Value> rooter(aCx, &data); SequenceRooter<JS::Value> rooter(aCx, &data);
if (!aTime.isUndefined()) { if (!aTime.isUndefined() && !data.AppendElement(aTime)) {
data.AppendElement(aTime); return;
} }
Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data); Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data);
@ -839,8 +843,8 @@ Console::TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime)
Sequence<JS::Value> data; Sequence<JS::Value> data;
SequenceRooter<JS::Value> rooter(aCx, &data); SequenceRooter<JS::Value> rooter(aCx, &data);
if (!aTime.isUndefined()) { if (!aTime.isUndefined() && !data.AppendElement(aTime)) {
data.AppendElement(aTime); return;
} }
Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data); Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data);
@ -852,8 +856,8 @@ Console::TimeStamp(JSContext* aCx, const JS::Handle<JS::Value> aData)
Sequence<JS::Value> data; Sequence<JS::Value> data;
SequenceRooter<JS::Value> rooter(aCx, &data); SequenceRooter<JS::Value> rooter(aCx, &data);
if (aData.isString()) { if (aData.isString() && !data.AppendElement(aData)) {
data.AppendElement(aData); return;
} }
Method(aCx, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data); Method(aCx, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data);
@ -892,7 +896,9 @@ Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
Sequence<JS::Value>& sequence = event.mArguments.Value(); Sequence<JS::Value>& sequence = event.mArguments.Value();
for (uint32_t i = 0; i < aData.Length(); ++i) { for (uint32_t i = 0; i < aData.Length(); ++i) {
sequence.AppendElement(aData[i]); if (!sequence.AppendElement(aData[i])) {
return;
}
} }
JS::Rooted<JS::Value> eventValue(aCx); JS::Rooted<JS::Value> eventValue(aCx);
@ -1293,13 +1299,18 @@ Console::ProcessCallData(ConsoleCallData* aData)
case MethodAssert: case MethodAssert:
event.mArguments.Construct(); event.mArguments.Construct();
event.mStyles.Construct(); event.mStyles.Construct();
ProcessArguments(cx, aData->mArguments, event.mArguments.Value(), if (!ProcessArguments(cx, aData->mArguments, event.mArguments.Value(),
event.mStyles.Value()); event.mStyles.Value())) {
return;
}
break; break;
default: default:
event.mArguments.Construct(); event.mArguments.Construct();
ArgumentsToValueList(aData->mArguments, event.mArguments.Value()); if (!ArgumentsToValueList(aData->mArguments, event.mArguments.Value())) {
return;
}
} }
if (aData->mMethodName == MethodGroup || if (aData->mMethodName == MethodGroup ||
@ -1418,48 +1429,52 @@ Console::ProcessCallData(ConsoleCallData* aData)
namespace { namespace {
// Helper method for ProcessArguments. Flushes output, if non-empty, to aSequence. // Helper method for ProcessArguments. Flushes output, if non-empty, to aSequence.
void bool
FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence, nsString &output) FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence, nsString &aOutput)
{ {
if (!output.IsEmpty()) { if (!aOutput.IsEmpty()) {
JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx, JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
output.get(), aOutput.get(),
output.Length())); aOutput.Length()));
if (!str) { if (!str) {
return; return false;
} }
aSequence.AppendElement(JS::StringValue(str)); if (!aSequence.AppendElement(JS::StringValue(str))) {
output.Truncate(); return false;
}
aOutput.Truncate();
} }
return true;
} }
} // anonymous namespace } // anonymous namespace
void bool
Console::ProcessArguments(JSContext* aCx, Console::ProcessArguments(JSContext* aCx,
const nsTArray<JS::Heap<JS::Value>>& aData, const nsTArray<JS::Heap<JS::Value>>& aData,
Sequence<JS::Value>& aSequence, Sequence<JS::Value>& aSequence,
Sequence<JS::Value>& aStyles) Sequence<JS::Value>& aStyles)
{ {
if (aData.IsEmpty()) { if (aData.IsEmpty()) {
return; return true;
} }
if (aData.Length() == 1 || !aData[0].isString()) { if (aData.Length() == 1 || !aData[0].isString()) {
ArgumentsToValueList(aData, aSequence); return ArgumentsToValueList(aData, aSequence);
return;
} }
JS::Rooted<JS::Value> format(aCx, aData[0]); JS::Rooted<JS::Value> format(aCx, aData[0]);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format)); JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
if (!jsString) { if (!jsString) {
return; return false;
} }
nsAutoJSString string; nsAutoJSString string;
if (!string.init(aCx, jsString)) { if (!string.init(aCx, jsString)) {
return; return false;
} }
nsString::const_iterator start, end; nsString::const_iterator start, end;
@ -1547,35 +1562,47 @@ Console::ProcessArguments(JSContext* aCx,
case 'o': case 'o':
case 'O': case 'O':
{ {
FlushOutput(aCx, aSequence, output); if (!FlushOutput(aCx, aSequence, output)) {
return false;
}
JS::Rooted<JS::Value> v(aCx); JS::Rooted<JS::Value> v(aCx);
if (index < aData.Length()) { if (index < aData.Length()) {
v = aData[index++]; v = aData[index++];
} }
aSequence.AppendElement(v); if (!aSequence.AppendElement(v)) {
return false;
}
break; break;
} }
case 'c': case 'c':
{ {
FlushOutput(aCx, aSequence, output); if (!FlushOutput(aCx, aSequence, output)) {
return false;
}
if (index < aData.Length()) { if (index < aData.Length()) {
JS::Rooted<JS::Value> v(aCx, aData[index++]); JS::Rooted<JS::Value> v(aCx, aData[index++]);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v)); JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
if (!jsString) { if (!jsString) {
return; return false;
} }
int32_t diff = aSequence.Length() - aStyles.Length(); int32_t diff = aSequence.Length() - aStyles.Length();
if (diff > 0) { if (diff > 0) {
for (int32_t i = 0; i < diff; i++) { for (int32_t i = 0; i < diff; i++) {
aStyles.AppendElement(JS::NullValue()); if (!aStyles.AppendElement(JS::NullValue())) {
return false;
}
} }
} }
aStyles.AppendElement(JS::StringValue(jsString));
if (!aStyles.AppendElement(JS::StringValue(jsString))) {
return false;
}
} }
break; break;
} }
@ -1585,12 +1612,12 @@ Console::ProcessArguments(JSContext* aCx,
JS::Rooted<JS::Value> value(aCx, aData[index++]); JS::Rooted<JS::Value> value(aCx, aData[index++]);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value)); JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
if (!jsString) { if (!jsString) {
return; return false;
} }
nsAutoJSString v; nsAutoJSString v;
if (!v.init(aCx, jsString)) { if (!v.init(aCx, jsString)) {
return; return false;
} }
output.Append(v); output.Append(v);
@ -1604,7 +1631,7 @@ Console::ProcessArguments(JSContext* aCx,
int32_t v; int32_t v;
if (!JS::ToInt32(aCx, value, &v)) { if (!JS::ToInt32(aCx, value, &v)) {
return; return false;
} }
nsCString format; nsCString format;
@ -1619,7 +1646,7 @@ Console::ProcessArguments(JSContext* aCx,
double v; double v;
if (!JS::ToNumber(aCx, value, &v)) { if (!JS::ToNumber(aCx, value, &v)) {
return; return false;
} }
nsCString format; nsCString format;
@ -1634,7 +1661,9 @@ Console::ProcessArguments(JSContext* aCx,
} }
} }
FlushOutput(aCx, aSequence, output); if (!FlushOutput(aCx, aSequence, output)) {
return false;
}
// Discard trailing style element if there is no output to apply it to. // Discard trailing style element if there is no output to apply it to.
if (aStyles.Length() > aSequence.Length()) { if (aStyles.Length() > aSequence.Length()) {
@ -1643,8 +1672,12 @@ Console::ProcessArguments(JSContext* aCx,
// The rest of the array, if unused by the format string. // The rest of the array, if unused by the format string.
for (; index < aData.Length(); ++index) { for (; index < aData.Length(); ++index) {
aSequence.AppendElement(aData[index]); if (!aSequence.AppendElement(aData[index])) {
return false;
}
} }
return true;
} }
void void
@ -1770,13 +1803,17 @@ Console::StopTimer(JSContext* aCx, const JS::Value& aName,
return value; return value;
} }
void bool
Console::ArgumentsToValueList(const nsTArray<JS::Heap<JS::Value>>& aData, Console::ArgumentsToValueList(const nsTArray<JS::Heap<JS::Value>>& aData,
Sequence<JS::Value>& aSequence) Sequence<JS::Value>& aSequence)
{ {
for (uint32_t i = 0; i < aData.Length(); ++i) { for (uint32_t i = 0; i < aData.Length(); ++i) {
aSequence.AppendElement(aData[i]); if (!aSequence.AppendElement(aData[i])) {
return false;
}
} }
return true;
} }
JS::Value JS::Value

View File

@ -158,7 +158,7 @@ private:
// finds based the format string. The index of the styles matches the indexes // finds based the format string. The index of the styles matches the indexes
// of elements that need the custom styling from aSequence. For elements with // of elements that need the custom styling from aSequence. For elements with
// no custom styling the array is padded with null elements. // no custom styling the array is padded with null elements.
void bool
ProcessArguments(JSContext* aCx, const nsTArray<JS::Heap<JS::Value>>& aData, ProcessArguments(JSContext* aCx, const nsTArray<JS::Heap<JS::Value>>& aData,
Sequence<JS::Value>& aSequence, Sequence<JS::Value>& aSequence,
Sequence<JS::Value>& aStyles); Sequence<JS::Value>& aStyles);
@ -182,7 +182,7 @@ private:
DOMHighResTimeStamp aTimestamp); DOMHighResTimeStamp aTimestamp);
// The method populates a Sequence from an array of JS::Value. // The method populates a Sequence from an array of JS::Value.
void bool
ArgumentsToValueList(const nsTArray<JS::Heap<JS::Value>>& aData, ArgumentsToValueList(const nsTArray<JS::Heap<JS::Value>>& aData,
Sequence<JS::Value>& aSequence); Sequence<JS::Value>& aSequence);

View File

@ -10,7 +10,7 @@
using namespace mozilla::dom; using namespace mozilla::dom;
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceEntry, mPerformance) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceEntry, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(PerformanceEntry) NS_IMPL_CYCLE_COLLECTING_ADDREF(PerformanceEntry)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PerformanceEntry) NS_IMPL_CYCLE_COLLECTING_RELEASE(PerformanceEntry)
@ -20,14 +20,15 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceEntry)
NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END NS_INTERFACE_MAP_END
PerformanceEntry::PerformanceEntry(nsPerformance* aPerformance, PerformanceEntry::PerformanceEntry(nsISupports* aParent,
const nsAString& aName, const nsAString& aName,
const nsAString& aEntryType) const nsAString& aEntryType)
: mPerformance(aPerformance), : mParent(aParent),
mName(aName), mName(aName),
mEntryType(aEntryType) mEntryType(aEntryType)
{ {
MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); // mParent is null in workers.
MOZ_ASSERT(mParent || !NS_IsMainThread());
} }
PerformanceEntry::~PerformanceEntry() PerformanceEntry::~PerformanceEntry()

View File

@ -7,9 +7,10 @@
#ifndef mozilla_dom_PerformanceEntry_h___ #ifndef mozilla_dom_PerformanceEntry_h___
#define mozilla_dom_PerformanceEntry_h___ #define mozilla_dom_PerformanceEntry_h___
#include "nsPerformance.h"
#include "nsDOMNavigationTiming.h" #include "nsDOMNavigationTiming.h"
class nsISupports;
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -21,7 +22,7 @@ protected:
virtual ~PerformanceEntry(); virtual ~PerformanceEntry();
public: public:
PerformanceEntry(nsPerformance* aPerformance, PerformanceEntry(nsISupports* aParent,
const nsAString& aName, const nsAString& aName,
const nsAString& aEntryType); const nsAString& aEntryType);
@ -30,9 +31,9 @@ public:
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
nsPerformance* GetParentObject() const nsISupports* GetParentObject() const
{ {
return mPerformance; return mParent;
} }
void GetName(nsAString& aName) const void GetName(nsAString& aName) const
@ -76,7 +77,7 @@ public:
} }
protected: protected:
nsRefPtr<nsPerformance> mPerformance; nsCOMPtr<nsISupports> mParent;
nsString mName; nsString mName;
nsString mEntryType; nsString mEntryType;
}; };

View File

@ -9,12 +9,14 @@
using namespace mozilla::dom; using namespace mozilla::dom;
PerformanceMark::PerformanceMark(nsPerformance* aPerformance, PerformanceMark::PerformanceMark(nsISupports* aParent,
const nsAString& aName) const nsAString& aName,
: PerformanceEntry(aPerformance, aName, NS_LITERAL_STRING("mark")) DOMHighResTimeStamp aStartTime)
: PerformanceEntry(aParent, aName, NS_LITERAL_STRING("mark"))
, mStartTime(aStartTime)
{ {
MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); // mParent is null in workers.
mStartTime = aPerformance->GetDOMTiming()->TimeStampToDOMHighRes(mozilla::TimeStamp::Now()); MOZ_ASSERT(mParent || !NS_IsMainThread());
} }
PerformanceMark::~PerformanceMark() PerformanceMark::~PerformanceMark()

View File

@ -16,8 +16,9 @@ namespace dom {
class PerformanceMark final : public PerformanceEntry class PerformanceMark final : public PerformanceEntry
{ {
public: public:
PerformanceMark(nsPerformance* aPerformance, PerformanceMark(nsISupports* aParent,
const nsAString& aName); const nsAString& aName,
DOMHighResTimeStamp aStartTime);
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;

View File

@ -9,15 +9,16 @@
using namespace mozilla::dom; using namespace mozilla::dom;
PerformanceMeasure::PerformanceMeasure(nsPerformance* aPerformance, PerformanceMeasure::PerformanceMeasure(nsISupports* aParent,
const nsAString& aName, const nsAString& aName,
DOMHighResTimeStamp aStartTime, DOMHighResTimeStamp aStartTime,
DOMHighResTimeStamp aEndTime) DOMHighResTimeStamp aEndTime)
: PerformanceEntry(aPerformance, aName, NS_LITERAL_STRING("measure")), : PerformanceEntry(aParent, aName, NS_LITERAL_STRING("measure")),
mStartTime(aStartTime), mStartTime(aStartTime),
mDuration(aEndTime - aStartTime) mDuration(aEndTime - aStartTime)
{ {
MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); // mParent is null in workers.
MOZ_ASSERT(mParent || !NS_IsMainThread());
} }
PerformanceMeasure::~PerformanceMeasure() PerformanceMeasure::~PerformanceMeasure()

View File

@ -16,7 +16,7 @@ namespace dom {
class PerformanceMeasure final : public PerformanceEntry class PerformanceMeasure final : public PerformanceEntry
{ {
public: public:
PerformanceMeasure(nsPerformance* aPerformance, PerformanceMeasure(nsISupports* aParent,
const nsAString& aName, const nsAString& aName,
DOMHighResTimeStamp aStartTime, DOMHighResTimeStamp aStartTime,
DOMHighResTimeStamp aEndTime); DOMHighResTimeStamp aEndTime);

View File

@ -960,7 +960,11 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv) ErrorResult& aRv)
{ {
Sequence<nsString> protocols; Sequence<nsString> protocols;
protocols.AppendElement(aProtocol); if (!protocols.AppendElement(aProtocol)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv); return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv);
} }

View File

@ -693,7 +693,9 @@ nsDOMMutationObserver::TakeRecords(
} }
void void
nsDOMMutationObserver::GetObservingInfo(nsTArray<Nullable<MutationObservingInfo> >& aResult) nsDOMMutationObserver::GetObservingInfo(
nsTArray<Nullable<MutationObservingInfo>>& aResult,
mozilla::ErrorResult& aRv)
{ {
aResult.SetCapacity(mReceivers.Count()); aResult.SetCapacity(mReceivers.Count());
for (int32_t i = 0; i < mReceivers.Count(); ++i) { for (int32_t i = 0; i < mReceivers.Count(); ++i) {
@ -712,7 +714,10 @@ nsDOMMutationObserver::GetObservingInfo(nsTArray<Nullable<MutationObservingInfo>
mozilla::dom::Sequence<nsString>& filtersAsStrings = mozilla::dom::Sequence<nsString>& filtersAsStrings =
info.mAttributeFilter.Value(); info.mAttributeFilter.Value();
for (int32_t j = 0; j < filters.Count(); ++j) { for (int32_t j = 0; j < filters.Count(); ++j) {
filtersAsStrings.AppendElement(nsDependentAtomString(filters[j])); if (!filtersAsStrings.AppendElement(nsDependentAtomString(filters[j]))) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
} }
} }
info.mObservedNode = mr->Target(); info.mObservedNode = mr->Target();

View File

@ -493,7 +493,8 @@ public:
void HandleMutation(); void HandleMutation();
void GetObservingInfo(nsTArray<Nullable<MutationObservingInfo> >& aResult); void GetObservingInfo(nsTArray<Nullable<MutationObservingInfo>>& aResult,
mozilla::ErrorResult& aRv);
mozilla::dom::MutationCallback* MutationCallback() { return mCallback; } mozilla::dom::MutationCallback* MutationCallback() { return mCallback; }

View File

@ -57,7 +57,8 @@ nsDOMNavigationTiming::TimeStampToDOM(mozilla::TimeStamp aStamp) const
return GetNavigationStart() + static_cast<int64_t>(duration.ToMilliseconds()); return GetNavigationStart() + static_cast<int64_t>(duration.ToMilliseconds());
} }
DOMTimeMilliSec nsDOMNavigationTiming::DurationFromStart(){ DOMTimeMilliSec nsDOMNavigationTiming::DurationFromStart()
{
return TimeStampToDOM(mozilla::TimeStamp::Now()); return TimeStampToDOM(mozilla::TimeStamp::Now());
} }

View File

@ -2455,6 +2455,33 @@ nsDOMWindowUtils::SetAsyncScrollOffset(nsIDOMNode* aNode,
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
nsDOMWindowUtils::SetAsyncZoom(nsIDOMNode* aRootElement, float aValue)
{
nsCOMPtr<Element> element = do_QueryInterface(aRootElement);
if (!element) {
return NS_ERROR_INVALID_ARG;
}
FrameMetrics::ViewID viewId;
if (!nsLayoutUtils::FindIDFor(element, &viewId)) {
return NS_ERROR_UNEXPECTED;
}
nsIWidget* widget = GetWidget();
if (!widget) {
return NS_ERROR_FAILURE;
}
LayerManager* manager = widget->GetLayerManager();
if (!manager) {
return NS_ERROR_FAILURE;
}
ShadowLayerForwarder* forwarder = manager->AsShadowForwarder();
if (!forwarder || !forwarder->HasShadowManager()) {
return NS_ERROR_UNEXPECTED;
}
forwarder->GetShadowManager()->SendSetAsyncZoom(viewId, aValue);
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
nsDOMWindowUtils::ComputeAnimationDistance(nsIDOMElement* aElement, nsDOMWindowUtils::ComputeAnimationDistance(nsIDOMElement* aElement,
const nsAString& aProperty, const nsAString& aProperty,

View File

@ -23,9 +23,12 @@
#include "mozilla/dom/PerformanceBinding.h" #include "mozilla/dom/PerformanceBinding.h"
#include "mozilla/dom/PerformanceTimingBinding.h" #include "mozilla/dom/PerformanceTimingBinding.h"
#include "mozilla/dom/PerformanceNavigationBinding.h" #include "mozilla/dom/PerformanceNavigationBinding.h"
#include "mozilla/Preferences.h"
#include "mozilla/IntegerPrintfMacros.h" #include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/TimeStamp.h" #include "mozilla/TimeStamp.h"
#include "js/HeapAPI.h" #include "js/HeapAPI.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#ifdef MOZ_WIDGET_GONK #ifdef MOZ_WIDGET_GONK
#define PERFLOG(msg, ...) __android_log_print(ANDROID_LOG_INFO, "PerformanceTiming", msg, ##__VA_ARGS__) #define PERFLOG(msg, ...) __android_log_print(ANDROID_LOG_INFO, "PerformanceTiming", msg, ##__VA_ARGS__)
@ -35,6 +38,7 @@
using namespace mozilla; using namespace mozilla;
using namespace mozilla::dom; using namespace mozilla::dom;
using namespace mozilla::dom::workers;
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPerformanceTiming, mPerformance) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPerformanceTiming, mPerformance)
@ -398,40 +402,36 @@ nsPerformanceNavigation::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenP
NS_IMPL_CYCLE_COLLECTION_CLASS(nsPerformance) NS_IMPL_CYCLE_COLLECTION_CLASS(nsPerformance)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsPerformance, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsPerformance, PerformanceBase)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow, mTiming, NS_IMPL_CYCLE_COLLECTION_UNLINK(mTiming,
mNavigation, mUserEntries, mNavigation,
mResourceEntries,
mParentPerformance) mParentPerformance)
tmp->mMozMemory = nullptr; tmp->mMozMemory = nullptr;
mozilla::DropJSObjects(this); mozilla::DropJSObjects(this);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsPerformance, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsPerformance, PerformanceBase)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mTiming, NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTiming,
mNavigation, mUserEntries, mNavigation,
mResourceEntries,
mParentPerformance) mParentPerformance)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsPerformance, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsPerformance, PerformanceBase)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory)
NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_ADDREF_INHERITED(nsPerformance, DOMEventTargetHelper) NS_IMPL_ADDREF_INHERITED(nsPerformance, PerformanceBase)
NS_IMPL_RELEASE_INHERITED(nsPerformance, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(nsPerformance, PerformanceBase)
nsPerformance::nsPerformance(nsPIDOMWindow* aWindow, nsPerformance::nsPerformance(nsPIDOMWindow* aWindow,
nsDOMNavigationTiming* aDOMTiming, nsDOMNavigationTiming* aDOMTiming,
nsITimedChannel* aChannel, nsITimedChannel* aChannel,
nsPerformance* aParentPerformance) nsPerformance* aParentPerformance)
: DOMEventTargetHelper(aWindow), : PerformanceBase(aWindow),
mWindow(aWindow),
mDOMTiming(aDOMTiming), mDOMTiming(aDOMTiming),
mChannel(aChannel), mChannel(aChannel),
mParentPerformance(aParentPerformance), mParentPerformance(aParentPerformance)
mResourceTimingBufferSize(kDefaultResourceTimingBufferSize)
{ {
MOZ_ASSERT(aWindow, "Parent window object should be provided"); MOZ_ASSERT(aWindow, "Parent window object should be provided");
} }
@ -499,7 +499,7 @@ nsPerformance::Navigation()
} }
DOMHighResTimeStamp DOMHighResTimeStamp
nsPerformance::Now() nsPerformance::Now() const
{ {
return GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now()); return GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now());
} }
@ -510,90 +510,6 @@ nsPerformance::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
return PerformanceBinding::Wrap(cx, this, aGivenProto); return PerformanceBinding::Wrap(cx, this, aGivenProto);
} }
void
nsPerformance::GetEntries(nsTArray<nsRefPtr<PerformanceEntry>>& retval)
{
MOZ_ASSERT(NS_IsMainThread());
retval = mResourceEntries;
retval.AppendElements(mUserEntries);
retval.Sort(PerformanceEntryComparator());
}
void
nsPerformance::GetEntriesByType(const nsAString& entryType,
nsTArray<nsRefPtr<PerformanceEntry>>& retval)
{
MOZ_ASSERT(NS_IsMainThread());
retval.Clear();
if (entryType.EqualsLiteral("resource")) {
retval = mResourceEntries;
} else if (entryType.EqualsLiteral("mark") ||
entryType.EqualsLiteral("measure")) {
for (PerformanceEntry* entry : mUserEntries) {
if (entry->GetEntryType().Equals(entryType)) {
retval.AppendElement(entry);
}
}
}
}
void
nsPerformance::GetEntriesByName(const nsAString& name,
const Optional<nsAString>& entryType,
nsTArray<nsRefPtr<PerformanceEntry>>& retval)
{
MOZ_ASSERT(NS_IsMainThread());
retval.Clear();
for (PerformanceEntry* entry : mResourceEntries) {
if (entry->GetName().Equals(name) &&
(!entryType.WasPassed() ||
entry->GetEntryType().Equals(entryType.Value()))) {
retval.AppendElement(entry);
}
}
for (PerformanceEntry* entry : mUserEntries) {
if (entry->GetName().Equals(name) &&
(!entryType.WasPassed() ||
entry->GetEntryType().Equals(entryType.Value()))) {
retval.AppendElement(entry);
}
}
retval.Sort(PerformanceEntryComparator());
}
void
nsPerformance::ClearUserEntries(const Optional<nsAString>& aEntryName,
const nsAString& aEntryType)
{
for (uint32_t i = 0; i < mUserEntries.Length();) {
if ((!aEntryName.WasPassed() ||
mUserEntries[i]->GetName().Equals(aEntryName.Value())) &&
(aEntryType.IsEmpty() ||
mUserEntries[i]->GetEntryType().Equals(aEntryType))) {
mUserEntries.RemoveElementAt(i);
} else {
++i;
}
}
}
void
nsPerformance::ClearResourceTimings()
{
MOZ_ASSERT(NS_IsMainThread());
mResourceEntries.Clear();
}
void
nsPerformance::SetResourceTimingBufferSize(uint64_t maxSize)
{
MOZ_ASSERT(NS_IsMainThread());
mResourceTimingBufferSize = maxSize;
}
/** /**
* An entry should be added only after the resource is loaded. * An entry should be added only after the resource is loaded.
* This method is not thread safe and can only be called on the main thread. * This method is not thread safe and can only be called on the main thread.
@ -609,7 +525,7 @@ nsPerformance::AddEntry(nsIHttpChannel* channel,
} }
// Don't add the entry if the buffer is full // Don't add the entry if the buffer is full
if (mResourceEntries.Length() >= mResourceTimingBufferSize) { if (IsResourceEntryLimitReached()) {
return; return;
} }
@ -652,174 +568,6 @@ nsPerformance::AddEntry(nsIHttpChannel* channel,
} }
} }
bool
nsPerformance::PerformanceEntryComparator::Equals(
const PerformanceEntry* aElem1,
const PerformanceEntry* aElem2) const
{
MOZ_ASSERT(aElem1 && aElem2,
"Trying to compare null performance entries");
return aElem1->StartTime() == aElem2->StartTime();
}
bool
nsPerformance::PerformanceEntryComparator::LessThan(
const PerformanceEntry* aElem1,
const PerformanceEntry* aElem2) const
{
MOZ_ASSERT(aElem1 && aElem2,
"Trying to compare null performance entries");
return aElem1->StartTime() < aElem2->StartTime();
}
void
nsPerformance::InsertResourceEntry(PerformanceEntry* aEntry)
{
MOZ_ASSERT(aEntry);
MOZ_ASSERT(mResourceEntries.Length() < mResourceTimingBufferSize);
if (mResourceEntries.Length() >= mResourceTimingBufferSize) {
return;
}
mResourceEntries.InsertElementSorted(aEntry,
PerformanceEntryComparator());
if (mResourceEntries.Length() == mResourceTimingBufferSize) {
// call onresourcetimingbufferfull
DispatchBufferFullEvent();
}
}
void
nsPerformance::InsertUserEntry(PerformanceEntry* aEntry)
{
if (nsContentUtils::IsUserTimingLoggingEnabled()) {
nsAutoCString uri;
nsresult rv = mWindow->GetDocumentURI()->GetHost(uri);
if(NS_FAILED(rv)) {
// If we have no URI, just put in "none".
uri.AssignLiteral("none");
}
PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64 "\n",
uri.get(),
NS_ConvertUTF16toUTF8(aEntry->GetEntryType()).get(),
NS_ConvertUTF16toUTF8(aEntry->GetName()).get(),
aEntry->StartTime(),
aEntry->Duration(),
static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC));
}
mUserEntries.InsertElementSorted(aEntry,
PerformanceEntryComparator());
}
void
nsPerformance::Mark(const nsAString& aName, ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
// Don't add the entry if the buffer is full. XXX should be removed by bug 1159003.
if (mUserEntries.Length() >= mResourceTimingBufferSize) {
return;
}
if (IsPerformanceTimingAttribute(aName)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
nsRefPtr<PerformanceMark> performanceMark =
new PerformanceMark(this, aName);
InsertUserEntry(performanceMark);
}
void
nsPerformance::ClearMarks(const Optional<nsAString>& aName)
{
MOZ_ASSERT(NS_IsMainThread());
ClearUserEntries(aName, NS_LITERAL_STRING("mark"));
}
DOMHighResTimeStamp
nsPerformance::ResolveTimestampFromName(const nsAString& aName,
ErrorResult& aRv)
{
nsAutoTArray<nsRefPtr<PerformanceEntry>, 1> arr;
DOMHighResTimeStamp ts;
Optional<nsAString> typeParam;
nsAutoString str;
str.AssignLiteral("mark");
typeParam = &str;
GetEntriesByName(aName, typeParam, arr);
if (!arr.IsEmpty()) {
return arr.LastElement()->StartTime();
}
if (!IsPerformanceTimingAttribute(aName)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return 0;
}
ts = GetPerformanceTimingFromString(aName);
if (!ts) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return 0;
}
return ConvertDOMMilliSecToHighRes(ts);
}
void
nsPerformance::Measure(const nsAString& aName,
const Optional<nsAString>& aStartMark,
const Optional<nsAString>& aEndMark,
ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
// Don't add the entry if the buffer is full. XXX should be removed by bug 1159003.
if (mUserEntries.Length() >= mResourceTimingBufferSize) {
return;
}
DOMHighResTimeStamp startTime;
DOMHighResTimeStamp endTime;
if (IsPerformanceTimingAttribute(aName)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
if (aStartMark.WasPassed()) {
startTime = ResolveTimestampFromName(aStartMark.Value(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
} else {
// Navigation start is used in this case, but since DOMHighResTimeStamp is
// in relation to navigation start, this will be zero if a name is not
// passed.
startTime = 0;
}
if (aEndMark.WasPassed()) {
endTime = ResolveTimestampFromName(aEndMark.Value(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
} else {
endTime = Now();
}
nsRefPtr<PerformanceMeasure> performanceMeasure =
new PerformanceMeasure(this, aName, startTime, endTime);
InsertUserEntry(performanceMeasure);
}
void
nsPerformance::ClearMeasures(const Optional<nsAString>& aName)
{
MOZ_ASSERT(NS_IsMainThread());
ClearUserEntries(aName, NS_LITERAL_STRING("measure"));
}
DOMHighResTimeStamp
nsPerformance::ConvertDOMMilliSecToHighRes(DOMTimeMilliSec aTime) {
// If the time we're trying to convert is equal to zero, it hasn't been set
// yet so just return 0.
if (aTime == 0) {
return 0;
}
return aTime - GetDOMTiming()->GetNavigationStart();
}
// To be removed once bug 1124165 lands // To be removed once bug 1124165 lands
bool bool
nsPerformance::IsPerformanceTimingAttribute(const nsAString& aName) nsPerformance::IsPerformanceTimingAttribute(const nsAString& aName)
@ -841,7 +589,7 @@ nsPerformance::IsPerformanceTimingAttribute(const nsAString& aName)
return false; return false;
} }
DOMTimeMilliSec DOMHighResTimeStamp
nsPerformance::GetPerformanceTimingFromString(const nsAString& aProperty) nsPerformance::GetPerformanceTimingFromString(const nsAString& aProperty)
{ {
if (!IsPerformanceTimingAttribute(aProperty)) { if (!IsPerformanceTimingAttribute(aProperty)) {
@ -913,3 +661,346 @@ nsPerformance::GetPerformanceTimingFromString(const nsAString& aProperty)
return 0; return 0;
} }
namespace {
// Helper classes
class MOZ_STACK_CLASS PerformanceEntryComparator final
{
public:
bool Equals(const PerformanceEntry* aElem1,
const PerformanceEntry* aElem2) const
{
MOZ_ASSERT(aElem1 && aElem2,
"Trying to compare null performance entries");
return aElem1->StartTime() == aElem2->StartTime();
}
bool LessThan(const PerformanceEntry* aElem1,
const PerformanceEntry* aElem2) const
{
MOZ_ASSERT(aElem1 && aElem2,
"Trying to compare null performance entries");
return aElem1->StartTime() < aElem2->StartTime();
}
};
class PrefEnabledRunnable final : public WorkerMainThreadRunnable
{
public:
explicit PrefEnabledRunnable(WorkerPrivate* aWorkerPrivate)
: WorkerMainThreadRunnable(aWorkerPrivate)
, mEnabled(false)
{ }
bool MainThreadRun() override
{
MOZ_ASSERT(NS_IsMainThread());
mEnabled = Preferences::GetBool("dom.enable_user_timing", false);
return true;
}
bool IsEnabled() const
{
return mEnabled;
}
private:
bool mEnabled;
};
} // anonymous namespace
/* static */ bool
nsPerformance::IsEnabled(JSContext* aCx, JSObject* aGlobal)
{
if (NS_IsMainThread()) {
return Preferences::GetBool("dom.enable_user_timing", false);
}
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
workerPrivate->AssertIsOnWorkerThread();
nsRefPtr<PrefEnabledRunnable> runnable =
new PrefEnabledRunnable(workerPrivate);
runnable->Dispatch(workerPrivate->GetJSContext());
return runnable->IsEnabled();
}
void
nsPerformance::InsertUserEntry(PerformanceEntry* aEntry)
{
MOZ_ASSERT(NS_IsMainThread());
if (nsContentUtils::IsUserTimingLoggingEnabled()) {
nsAutoCString uri;
nsresult rv = GetOwner()->GetDocumentURI()->GetHost(uri);
if(NS_FAILED(rv)) {
// If we have no URI, just put in "none".
uri.AssignLiteral("none");
}
PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64 "\n",
uri.get(),
NS_ConvertUTF16toUTF8(aEntry->GetEntryType()).get(),
NS_ConvertUTF16toUTF8(aEntry->GetName()).get(),
aEntry->StartTime(),
aEntry->Duration(),
static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC));
}
PerformanceBase::InsertUserEntry(aEntry);
}
DOMHighResTimeStamp
nsPerformance::DeltaFromNavigationStart(DOMHighResTimeStamp aTime)
{
// If the time we're trying to convert is equal to zero, it hasn't been set
// yet so just return 0.
if (aTime == 0) {
return 0;
}
return aTime - GetDOMTiming()->GetNavigationStart();
}
// PerformanceBase
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PerformanceBase)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_INHERITED(PerformanceBase,
DOMEventTargetHelper,
mUserEntries,
mResourceEntries);
NS_IMPL_ADDREF_INHERITED(PerformanceBase, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(PerformanceBase, DOMEventTargetHelper)
PerformanceBase::PerformanceBase()
: mResourceTimingBufferSize(kDefaultResourceTimingBufferSize)
{
MOZ_ASSERT(!NS_IsMainThread());
}
PerformanceBase::PerformanceBase(nsPIDOMWindow* aWindow)
: DOMEventTargetHelper(aWindow)
, mResourceTimingBufferSize(kDefaultResourceTimingBufferSize)
{
MOZ_ASSERT(NS_IsMainThread());
}
PerformanceBase::~PerformanceBase()
{}
void
PerformanceBase::GetEntries(nsTArray<nsRefPtr<PerformanceEntry>>& aRetval)
{
aRetval = mResourceEntries;
aRetval.AppendElements(mUserEntries);
aRetval.Sort(PerformanceEntryComparator());
}
void
PerformanceBase::GetEntriesByType(const nsAString& aEntryType,
nsTArray<nsRefPtr<PerformanceEntry>>& aRetval)
{
if (aEntryType.EqualsLiteral("resource")) {
aRetval = mResourceEntries;
return;
}
aRetval.Clear();
if (aEntryType.EqualsLiteral("mark") ||
aEntryType.EqualsLiteral("measure")) {
for (PerformanceEntry* entry : mUserEntries) {
if (entry->GetEntryType().Equals(aEntryType)) {
aRetval.AppendElement(entry);
}
}
}
}
void
PerformanceBase::GetEntriesByName(const nsAString& aName,
const Optional<nsAString>& aEntryType,
nsTArray<nsRefPtr<PerformanceEntry>>& aRetval)
{
aRetval.Clear();
for (PerformanceEntry* entry : mResourceEntries) {
if (entry->GetName().Equals(aName) &&
(!aEntryType.WasPassed() ||
entry->GetEntryType().Equals(aEntryType.Value()))) {
aRetval.AppendElement(entry);
}
}
for (PerformanceEntry* entry : mUserEntries) {
if (entry->GetName().Equals(aName) &&
(!aEntryType.WasPassed() ||
entry->GetEntryType().Equals(aEntryType.Value()))) {
aRetval.AppendElement(entry);
}
}
aRetval.Sort(PerformanceEntryComparator());
}
void
PerformanceBase::ClearUserEntries(const Optional<nsAString>& aEntryName,
const nsAString& aEntryType)
{
for (uint32_t i = 0; i < mUserEntries.Length();) {
if ((!aEntryName.WasPassed() ||
mUserEntries[i]->GetName().Equals(aEntryName.Value())) &&
(aEntryType.IsEmpty() ||
mUserEntries[i]->GetEntryType().Equals(aEntryType))) {
mUserEntries.RemoveElementAt(i);
} else {
++i;
}
}
}
void
PerformanceBase::ClearResourceTimings()
{
MOZ_ASSERT(NS_IsMainThread());
mResourceEntries.Clear();
}
void
PerformanceBase::Mark(const nsAString& aName, ErrorResult& aRv)
{
// Don't add the entry if the buffer is full. XXX should be removed by bug 1159003.
if (mUserEntries.Length() >= mResourceTimingBufferSize) {
return;
}
if (IsPerformanceTimingAttribute(aName)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
nsRefPtr<PerformanceMark> performanceMark =
new PerformanceMark(GetAsISupports(), aName, Now());
InsertUserEntry(performanceMark);
}
void
PerformanceBase::ClearMarks(const Optional<nsAString>& aName)
{
ClearUserEntries(aName, NS_LITERAL_STRING("mark"));
}
DOMHighResTimeStamp
PerformanceBase::ResolveTimestampFromName(const nsAString& aName,
ErrorResult& aRv)
{
nsAutoTArray<nsRefPtr<PerformanceEntry>, 1> arr;
DOMHighResTimeStamp ts;
Optional<nsAString> typeParam;
nsAutoString str;
str.AssignLiteral("mark");
typeParam = &str;
GetEntriesByName(aName, typeParam, arr);
if (!arr.IsEmpty()) {
return arr.LastElement()->StartTime();
}
if (!IsPerformanceTimingAttribute(aName)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return 0;
}
ts = GetPerformanceTimingFromString(aName);
if (!ts) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return 0;
}
return DeltaFromNavigationStart(ts);
}
void
PerformanceBase::Measure(const nsAString& aName,
const Optional<nsAString>& aStartMark,
const Optional<nsAString>& aEndMark,
ErrorResult& aRv)
{
// Don't add the entry if the buffer is full. XXX should be removed by bug
// 1159003.
if (mUserEntries.Length() >= mResourceTimingBufferSize) {
return;
}
DOMHighResTimeStamp startTime;
DOMHighResTimeStamp endTime;
if (IsPerformanceTimingAttribute(aName)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
if (aStartMark.WasPassed()) {
startTime = ResolveTimestampFromName(aStartMark.Value(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
} else {
// Navigation start is used in this case, but since DOMHighResTimeStamp is
// in relation to navigation start, this will be zero if a name is not
// passed.
startTime = 0;
}
if (aEndMark.WasPassed()) {
endTime = ResolveTimestampFromName(aEndMark.Value(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
} else {
endTime = Now();
}
nsRefPtr<PerformanceMeasure> performanceMeasure =
new PerformanceMeasure(GetAsISupports(), aName, startTime, endTime);
InsertUserEntry(performanceMeasure);
}
void
PerformanceBase::ClearMeasures(const Optional<nsAString>& aName)
{
ClearUserEntries(aName, NS_LITERAL_STRING("measure"));
}
void
PerformanceBase::InsertUserEntry(PerformanceEntry* aEntry)
{
mUserEntries.InsertElementSorted(aEntry,
PerformanceEntryComparator());
}
void
PerformanceBase::SetResourceTimingBufferSize(uint64_t aMaxSize)
{
mResourceTimingBufferSize = aMaxSize;
}
void
PerformanceBase::InsertResourceEntry(PerformanceEntry* aEntry)
{
MOZ_ASSERT(aEntry);
MOZ_ASSERT(mResourceEntries.Length() < mResourceTimingBufferSize);
if (mResourceEntries.Length() >= mResourceTimingBufferSize) {
return;
}
mResourceEntries.InsertElementSorted(aEntry,
PerformanceEntryComparator());
if (mResourceEntries.Length() == mResourceTimingBufferSize) {
// call onresourcetimingbufferfull
DispatchBufferFullEvent();
}
}

View File

@ -288,18 +288,90 @@ private:
nsRefPtr<nsPerformance> mPerformance; nsRefPtr<nsPerformance> mPerformance;
}; };
// Script "performance" object // Base class for main-thread and worker Performance API
class nsPerformance final : public mozilla::DOMEventTargetHelper class PerformanceBase : public mozilla::DOMEventTargetHelper
{ {
public: public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PerformanceBase,
DOMEventTargetHelper)
PerformanceBase();
explicit PerformanceBase(nsPIDOMWindow* aWindow);
typedef mozilla::dom::PerformanceEntry PerformanceEntry; typedef mozilla::dom::PerformanceEntry PerformanceEntry;
void GetEntries(nsTArray<nsRefPtr<PerformanceEntry>>& aRetval);
void GetEntriesByType(const nsAString& aEntryType,
nsTArray<nsRefPtr<PerformanceEntry>>& aRetval);
void GetEntriesByName(const nsAString& aName,
const mozilla::dom::Optional<nsAString>& aEntryType,
nsTArray<nsRefPtr<PerformanceEntry>>& aRetval);
void ClearResourceTimings();
virtual DOMHighResTimeStamp Now() const = 0;
void Mark(const nsAString& aName, mozilla::ErrorResult& aRv);
void ClearMarks(const mozilla::dom::Optional<nsAString>& aName);
void Measure(const nsAString& aName,
const mozilla::dom::Optional<nsAString>& aStartMark,
const mozilla::dom::Optional<nsAString>& aEndMark,
mozilla::ErrorResult& aRv);
void ClearMeasures(const mozilla::dom::Optional<nsAString>& aName);
void SetResourceTimingBufferSize(uint64_t aMaxSize);
protected:
virtual ~PerformanceBase();
virtual void InsertUserEntry(PerformanceEntry* aEntry);
void InsertResourceEntry(PerformanceEntry* aEntry);
void ClearUserEntries(const mozilla::dom::Optional<nsAString>& aEntryName,
const nsAString& aEntryType);
DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName,
mozilla::ErrorResult& aRv);
virtual nsISupports* GetAsISupports() = 0;
virtual void DispatchBufferFullEvent() = 0;
virtual DOMHighResTimeStamp
DeltaFromNavigationStart(DOMHighResTimeStamp aTime) = 0;
virtual bool IsPerformanceTimingAttribute(const nsAString& aName) = 0;
virtual DOMHighResTimeStamp
GetPerformanceTimingFromString(const nsAString& aTimingName) = 0;
bool IsResourceEntryLimitReached() const
{
return mResourceEntries.Length() >= mResourceTimingBufferSize;
}
private:
nsTArray<nsRefPtr<PerformanceEntry>> mUserEntries;
nsTArray<nsRefPtr<PerformanceEntry>> mResourceEntries;
uint64_t mResourceTimingBufferSize;
static const uint64_t kDefaultResourceTimingBufferSize = 150;
};
// Script "performance" object
class nsPerformance final : public PerformanceBase
{
public:
nsPerformance(nsPIDOMWindow* aWindow, nsPerformance(nsPIDOMWindow* aWindow,
nsDOMNavigationTiming* aDOMTiming, nsDOMNavigationTiming* aDOMTiming,
nsITimedChannel* aChannel, nsITimedChannel* aChannel,
nsPerformance* aParentPerformance); nsPerformance* aParentPerformance);
NS_DECL_ISUPPORTS_INHERITED NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsPerformance, DOMEventTargetHelper) NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsPerformance,
PerformanceBase)
static bool IsEnabled(JSContext* aCx, JSObject* aGlobal);
nsDOMNavigationTiming* GetDOMTiming() const nsDOMNavigationTiming* GetDOMTiming() const
{ {
@ -316,35 +388,28 @@ public:
return mParentPerformance; return mParentPerformance;
} }
nsPIDOMWindow* GetParentObject() const JSObject* WrapObject(JSContext *cx,
{ JS::Handle<JSObject*> aGivenProto) override;
return mWindow.get();
}
virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
// Performance WebIDL methods // Performance WebIDL methods
DOMHighResTimeStamp Now(); DOMHighResTimeStamp Now() const override;
nsPerformanceTiming* Timing(); nsPerformanceTiming* Timing();
nsPerformanceNavigation* Navigation(); nsPerformanceNavigation* Navigation();
void GetEntries(nsTArray<nsRefPtr<PerformanceEntry>>& retval);
void GetEntriesByType(const nsAString& entryType,
nsTArray<nsRefPtr<PerformanceEntry>>& retval);
void GetEntriesByName(const nsAString& name,
const mozilla::dom::Optional< nsAString >& entryType,
nsTArray<nsRefPtr<PerformanceEntry>>& retval);
void AddEntry(nsIHttpChannel* channel, void AddEntry(nsIHttpChannel* channel,
nsITimedChannel* timedChannel); nsITimedChannel* timedChannel);
void ClearResourceTimings();
void SetResourceTimingBufferSize(uint64_t maxSize); using PerformanceBase::GetEntries;
void Mark(const nsAString& aName, mozilla::ErrorResult& aRv); using PerformanceBase::GetEntriesByType;
void ClearMarks(const mozilla::dom::Optional<nsAString>& aName); using PerformanceBase::GetEntriesByName;
void Measure(const nsAString& aName, using PerformanceBase::ClearResourceTimings;
const mozilla::dom::Optional<nsAString>& aStartMark,
const mozilla::dom::Optional<nsAString>& aEndMark, using PerformanceBase::Mark;
mozilla::ErrorResult& aRv); using PerformanceBase::ClearMarks;
void ClearMeasures(const mozilla::dom::Optional<nsAString>& aName); using PerformanceBase::Measure;
using PerformanceBase::ClearMeasures;
using PerformanceBase::SetResourceTimingBufferSize;
void GetMozMemory(JSContext *aCx, JS::MutableHandle<JSObject*> aObj); void GetMozMemory(JSContext *aCx, JS::MutableHandle<JSObject*> aObj);
@ -352,36 +417,30 @@ public:
private: private:
~nsPerformance(); ~nsPerformance();
bool IsPerformanceTimingAttribute(const nsAString& aName);
DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName, mozilla::ErrorResult& aRv); nsISupports* GetAsISupports() override
DOMTimeMilliSec GetPerformanceTimingFromString(const nsAString& aTimingName); {
DOMHighResTimeStamp ConvertDOMMilliSecToHighRes(const DOMTimeMilliSec aTime); return this;
void DispatchBufferFullEvent(); }
void InsertUserEntry(PerformanceEntry* aEntry); void InsertUserEntry(PerformanceEntry* aEntry);
void ClearUserEntries(const mozilla::dom::Optional<nsAString>& aEntryName,
const nsAString& aEntryType); bool IsPerformanceTimingAttribute(const nsAString& aName) override;
void InsertResourceEntry(PerformanceEntry* aEntry);
nsCOMPtr<nsPIDOMWindow> mWindow; DOMHighResTimeStamp
DeltaFromNavigationStart(DOMHighResTimeStamp aTime) override;
DOMHighResTimeStamp
GetPerformanceTimingFromString(const nsAString& aTimingName) override;
void DispatchBufferFullEvent() override;
nsRefPtr<nsDOMNavigationTiming> mDOMTiming; nsRefPtr<nsDOMNavigationTiming> mDOMTiming;
nsCOMPtr<nsITimedChannel> mChannel; nsCOMPtr<nsITimedChannel> mChannel;
nsRefPtr<nsPerformanceTiming> mTiming; nsRefPtr<nsPerformanceTiming> mTiming;
nsRefPtr<nsPerformanceNavigation> mNavigation; nsRefPtr<nsPerformanceNavigation> mNavigation;
nsTArray<nsRefPtr<PerformanceEntry>> mResourceEntries;
nsTArray<nsRefPtr<PerformanceEntry>> mUserEntries;
nsRefPtr<nsPerformance> mParentPerformance; nsRefPtr<nsPerformance> mParentPerformance;
uint64_t mResourceTimingBufferSize;
JS::Heap<JSObject*> mMozMemory; JS::Heap<JSObject*> mMozMemory;
static const uint64_t kDefaultResourceTimingBufferSize = 150;
// Helper classes
class PerformanceEntryComparator {
public:
bool Equals(const PerformanceEntry* aElem1,
const PerformanceEntry* aElem2) const;
bool LessThan(const PerformanceEntry* aElem1,
const PerformanceEntry* aElem2) const;
};
}; };
inline nsDOMNavigationTiming* inline nsDOMNavigationTiming*

View File

@ -119,20 +119,29 @@ NS_IMPL_ISUPPORTS(
nsISupportsWeakReference, nsISupportsWeakReference,
nsIMemoryReporter) nsIMemoryReporter)
static const PLDHashTableOps hash_table_ops =
{
GlobalNameHashHashKey,
GlobalNameHashMatchEntry,
PL_DHashMoveEntryStub,
GlobalNameHashClearEntry,
GlobalNameHashInitEntry
};
#define GLOBALNAME_HASHTABLE_INITIAL_LENGTH 512
nsScriptNameSpaceManager::nsScriptNameSpaceManager() nsScriptNameSpaceManager::nsScriptNameSpaceManager()
: mIsInitialized(false) : mGlobalNames(&hash_table_ops, sizeof(GlobalNameMapEntry),
GLOBALNAME_HASHTABLE_INITIAL_LENGTH)
, mNavigatorNames(&hash_table_ops, sizeof(GlobalNameMapEntry),
GLOBALNAME_HASHTABLE_INITIAL_LENGTH)
{ {
MOZ_COUNT_CTOR(nsScriptNameSpaceManager); MOZ_COUNT_CTOR(nsScriptNameSpaceManager);
} }
nsScriptNameSpaceManager::~nsScriptNameSpaceManager() nsScriptNameSpaceManager::~nsScriptNameSpaceManager()
{ {
if (mIsInitialized) { UnregisterWeakMemoryReporter(this);
UnregisterWeakMemoryReporter(this);
// Destroy the hash
PL_DHashTableFinish(&mGlobalNames);
PL_DHashTableFinish(&mNavigatorNames);
}
MOZ_COUNT_DTOR(nsScriptNameSpaceManager); MOZ_COUNT_DTOR(nsScriptNameSpaceManager);
} }
@ -309,30 +318,9 @@ nsScriptNameSpaceManager::RegisterInterface(const char* aIfName,
return NS_OK; return NS_OK;
} }
#define GLOBALNAME_HASHTABLE_INITIAL_LENGTH 512
nsresult nsresult
nsScriptNameSpaceManager::Init() nsScriptNameSpaceManager::Init()
{ {
static const PLDHashTableOps hash_table_ops =
{
GlobalNameHashHashKey,
GlobalNameHashMatchEntry,
PL_DHashMoveEntryStub,
GlobalNameHashClearEntry,
GlobalNameHashInitEntry
};
PL_DHashTableInit(&mGlobalNames, &hash_table_ops,
sizeof(GlobalNameMapEntry),
GLOBALNAME_HASHTABLE_INITIAL_LENGTH);
PL_DHashTableInit(&mNavigatorNames, &hash_table_ops,
sizeof(GlobalNameMapEntry),
GLOBALNAME_HASHTABLE_INITIAL_LENGTH);
mIsInitialized = true;
RegisterWeakMemoryReporter(this); RegisterWeakMemoryReporter(this);
nsresult rv = NS_OK; nsresult rv = NS_OK;

View File

@ -234,10 +234,8 @@ private:
nsGlobalNameStruct* LookupNameInternal(const nsAString& aName, nsGlobalNameStruct* LookupNameInternal(const nsAString& aName,
const char16_t **aClassName = nullptr); const char16_t **aClassName = nullptr);
PLDHashTable mGlobalNames; PLDHashTable2 mGlobalNames;
PLDHashTable mNavigatorNames; PLDHashTable2 mNavigatorNames;
bool mIsInitialized;
}; };
#endif /* nsScriptNameSpaceManager_h__ */ #endif /* nsScriptNameSpaceManager_h__ */

View File

@ -80,6 +80,7 @@ function createTest(schemeFrom, schemeTo, policy) {
<iframe src="' + _create2ndLevelIframeUrl('form') + '"></iframe>\n\ <iframe src="' + _create2ndLevelIframeUrl('form') + '"></iframe>\n\
<iframe src="' + _create2ndLevelIframeUrl('window.location') + '"></iframe>\n\ <iframe src="' + _create2ndLevelIframeUrl('window.location') + '"></iframe>\n\
<script>\n\ <script>\n\
var _testFinished = 0\n\
(function() {\n\ (function() {\n\
var x = new XMLHttpRequest();\n\ var x = new XMLHttpRequest();\n\
x.open("GET", "' + _createTestUrl('xmlhttprequest') + '");\n\ x.open("GET", "' + _createTestUrl('xmlhttprequest') + '");\n\
@ -113,8 +114,10 @@ function createTest(schemeFrom, schemeTo, policy) {
// called by the two things that must complete: window.open page // called by the two things that must complete: window.open page
// and the window load event. When both are complete, this // and the window load event. When both are complete, this
// "finishes" the iframe subtest by clicking the link. // "finishes" the iframe subtest by clicking the link.
// _testFinished avoids calling this function twice (which may happen)
'function checkForFinish() {\n\ 'function checkForFinish() {\n\
if (window._isLoaded && window._openedWindowLoaded) {\n\ if (window._isLoaded && window._openedWindowLoaded && !window._testFinished) {\n\
window._testFinished = 1;\n\
document.getElementById("link").click();\n\ document.getElementById("link").click();\n\
}\n\ }\n\
}\n\ }\n\

View File

@ -243,6 +243,7 @@ support-files =
wholeTexty-helper.xml wholeTexty-helper.xml
file_nonascii_blob_url.html file_nonascii_blob_url.html
referrerHelper.js referrerHelper.js
test_performance_user_timing.js
[test_anonymousContent_api.html] [test_anonymousContent_api.html]
[test_anonymousContent_append_after_reflow.html] [test_anonymousContent_append_after_reflow.html]
@ -611,8 +612,16 @@ skip-if = buildapp == 'b2g'
[test_bug698381.html] [test_bug698381.html]
[test_bug698384.html] [test_bug698384.html]
[test_bug704063.html] [test_bug704063.html]
[test_bug704320.html] [test_bug704320_http_http.html]
skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # b2g (Needs multiple window.open support) android(times out, bug 1100609) e10s(randomly fails, bug 1100362) support-files = referrerHelper.js
[test_bug704320_http_https.html]
support-files = referrerHelper.js
[test_bug704320_https_http.html]
support-files = referrerHelper.js
skip-if = buildapp == 'b2g' # b2g (https://example.com not working bug 1162353)
[test_bug704320_https_https.html]
support-files = referrerHelper.js
skip-if = buildapp == 'b2g' # b2g (https://example.com not working bug 1162353)
[test_bug704320_policyset.html] [test_bug704320_policyset.html]
support-files = referrerHelper.js support-files = referrerHelper.js
[test_bug704320_preload.html] [test_bug704320_preload.html]

View File

@ -19,7 +19,6 @@ window.addEventListener("message", function(event) {
/** /**
* helper to perform an XHR. * helper to perform an XHR.
* Used by resetCounter() and checkResults().
*/ */
function doXHR(url, onSuccess, onFail) { function doXHR(url, onSuccess, onFail) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
@ -51,7 +50,7 @@ function resetCounter() {
/** /**
* Grabs the results via XHR and passes to checker. * Grabs the results via XHR and passes to checker.
*/ */
function checkResults(testname, expected) { function checkIndividualResults(testname, expected) {
doXHR('/tests/dom/base/test/bug704320_counter.sjs?results', doXHR('/tests/dom/base/test/bug704320_counter.sjs?results',
function(xhr) { function(xhr) {
var results = JSON.parse(xhr.responseText); var results = JSON.parse(xhr.responseText);
@ -74,3 +73,163 @@ function checkResults(testname, expected) {
SimpleTest.finish(); SimpleTest.finish();
}); });
} }
/**
* Grabs the results via XHR and checks them
*/
function checkExpectedGlobalResults() {
var url = 'bug704320.sjs?action=get-test-results';
doXHR(url,
function(xhr) {
var response = JSON.parse(xhr.response);
for (type in response) {
for (scheme in response[type]) {
for (policy in response[type][scheme]) {
var expectedResult = EXPECTED_RESULTS[type] === undefined ?
EXPECTED_RESULTS['default'][scheme][policy] :
EXPECTED_RESULTS[type][scheme][policy];
is(response[type][scheme][policy], expectedResult, type + ' ' + scheme + ' ' + policy);
}
}
}
advance();
},
function(xhr) {
ok(false, "Can't get results from the counter server.");
SimpleTest.finish();
});
}
var EXPECTED_RESULTS = {
// From docshell/base/nsDocShell.cpp:
// "If the document containing the hyperlink being audited was not retrieved
// over an encrypted connection and its address does not have the same
// origin as "ping URL", send a referrer."
'link-ping': {
// Same-origin
'http-to-http': {
'no-referrer': '',
'unsafe-url': '',
'origin': '',
'origin-when-cross-origin': '',
'no-referrer-when-downgrade': ''
},
'http-to-https': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade'
},
// Encrypted and not same-origin
'https-to-http': {
'no-referrer': '',
'unsafe-url': '',
'origin': '',
'origin-when-cross-origin': '',
'no-referrer-when-downgrade': ''
},
// Encrypted
'https-to-https': {
'no-referrer': '',
'unsafe-url': '',
'origin': '',
'origin-when-cross-origin': '',
'no-referrer-when-downgrade': ''
}
},
// form is tested in a 2nd level iframe.
'form': {
'http-to-http': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=unsafe-url&type=form',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=origin-when-cross-origin&type=form',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer-when-downgrade&type=form'
},
'http-to-https': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url&type=form',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade&type=form'
},
'https-to-http': {
'no-referrer': '',
'unsafe-url': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=http&policy=unsafe-url&type=form',
'origin': 'https://example.com',
'origin-when-cross-origin': 'https://example.com',
'no-referrer-when-downgrade': ''
},
'https-to-https': {
'no-referrer': '',
'unsafe-url': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=unsafe-url&type=form',
'origin': 'https://example.com',
'origin-when-cross-origin': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=origin-when-cross-origin&type=form',
'no-referrer-when-downgrade': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer-when-downgrade&type=form'
}
},
// window.location is tested in a 2nd level iframe.
'window.location': {
'http-to-http': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=unsafe-url&type=window.location',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=origin-when-cross-origin&type=window.location',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer-when-downgrade&type=window.location'
},
'http-to-https': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url&type=window.location',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade&type=window.location'
},
'https-to-http': {
'no-referrer': '',
'unsafe-url': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=http&policy=unsafe-url&type=window.location',
'origin': 'https://example.com',
'origin-when-cross-origin': 'https://example.com',
'no-referrer-when-downgrade': ''
},
'https-to-https': {
'no-referrer': '',
'unsafe-url': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=unsafe-url&type=window.location',
'origin': 'https://example.com',
'origin-when-cross-origin': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=origin-when-cross-origin&type=window.location',
'no-referrer-when-downgrade': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer-when-downgrade&type=window.location'
}
},
'default': {
'http-to-http': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=unsafe-url',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=origin-when-cross-origin',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer-when-downgrade'
},
'http-to-https': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade'
},
'https-to-http': {
'no-referrer': '',
'unsafe-url': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=unsafe-url',
'origin': 'https://example.com',
'origin-when-cross-origin': 'https://example.com',
'no-referrer-when-downgrade': ''
},
'https-to-https': {
'no-referrer': '',
'unsafe-url': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=unsafe-url',
'origin': 'https://example.com',
'origin-when-cross-origin': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=origin-when-cross-origin',
'no-referrer-when-downgrade': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer-when-downgrade'
}
}
};

View File

@ -27,7 +27,7 @@ var tests = (function() {
// origin when crossorigin (trimming whitespace) // origin when crossorigin (trimming whitespace)
yield resetCounter(); yield resetCounter();
yield iframe.src = sjs + "&policy=" + escape(' origin-when-crossorigin'); yield iframe.src = sjs + "&policy=" + escape(' origin-when-crossorigin');
yield checkResults("origin-when-cross-origin", ["origin", "full"]); yield checkIndividualResults("origin-when-cross-origin", ["origin", "full"]);
// complete. Be sure to yield so we don't call this twice. // complete. Be sure to yield so we don't call this twice.
yield SimpleTest.finish(); yield SimpleTest.finish();

View File

@ -1,252 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=704320
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 704320</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=704320">Mozilla Bug 704320</a>
<p id="display"></p>
<pre id="content">
</pre>
<pre id="test">
</pre>
<script type="application/javascript">
var testIframeUrls = [
// HTTP to HTTP
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer-when-downgrade',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=unsafe-url',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=origin',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=origin-when-cross-origin',
// HTTP to HTTPS
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=origin',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=origin-when-cross-origin',
// HTTPS to HTTP
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=no-referrer-when-downgrade',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=no-referrer',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=unsafe-url',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=origin',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=origin-when-cross-origin',
// HTTPS to HTTPS
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer-when-downgrade',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=unsafe-url',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=origin',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=origin-when-cross-origin'
];
var expectedResults = {
// From docshell/base/nsDocShell.cpp:
// "If the document containing the hyperlink being audited was not retrieved
// over an encrypted connection and its address does not have the same
// origin as "ping URL", send a referrer."
'link-ping': {
// Same-origin
'http-to-http': {
'no-referrer': '',
'unsafe-url': '',
'origin': '',
'origin-when-cross-origin': '',
'no-referrer-when-downgrade': ''
},
'http-to-https': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade'
},
// Encrypted and not same-origin
'https-to-http': {
'no-referrer': '',
'unsafe-url': '',
'origin': '',
'origin-when-cross-origin': '',
'no-referrer-when-downgrade': ''
},
// Encrypted
'https-to-https': {
'no-referrer': '',
'unsafe-url': '',
'origin': '',
'origin-when-cross-origin': '',
'no-referrer-when-downgrade': ''
}
},
// form is tested in a 2nd level iframe.
'form': {
'http-to-http': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=unsafe-url&type=form',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=origin-when-cross-origin&type=form',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer-when-downgrade&type=form'
},
'http-to-https': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url&type=form',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade&type=form'
},
'https-to-http': {
'no-referrer': '',
'unsafe-url': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=http&policy=unsafe-url&type=form',
'origin': 'https://example.com',
'origin-when-cross-origin': 'https://example.com',
'no-referrer-when-downgrade': ''
},
'https-to-https': {
'no-referrer': '',
'unsafe-url': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=unsafe-url&type=form',
'origin': 'https://example.com',
'origin-when-cross-origin': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=origin-when-cross-origin&type=form',
'no-referrer-when-downgrade': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer-when-downgrade&type=form'
}
},
// window.location is tested in a 2nd level iframe.
'window.location': {
'http-to-http': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=unsafe-url&type=window.location',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=origin-when-cross-origin&type=window.location',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer-when-downgrade&type=window.location'
},
'http-to-https': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url&type=window.location',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade&type=window.location'
},
'https-to-http': {
'no-referrer': '',
'unsafe-url': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=http&policy=unsafe-url&type=window.location',
'origin': 'https://example.com',
'origin-when-cross-origin': 'https://example.com',
'no-referrer-when-downgrade': ''
},
'https-to-https': {
'no-referrer': '',
'unsafe-url': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=unsafe-url&type=window.location',
'origin': 'https://example.com',
'origin-when-cross-origin': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=origin-when-cross-origin&type=window.location',
'no-referrer-when-downgrade': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-2nd-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer-when-downgrade&type=window.location'
}
},
'default': {
'http-to-http': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=unsafe-url',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=origin-when-cross-origin',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer-when-downgrade'
},
'http-to-https': {
'no-referrer': '',
'unsafe-url': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url',
'origin': 'http://example.com',
'origin-when-cross-origin': 'http://example.com',
'no-referrer-when-downgrade': 'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade'
},
'https-to-http': {
'no-referrer': '',
'unsafe-url': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=unsafe-url',
'origin': 'https://example.com',
'origin-when-cross-origin': 'https://example.com',
'no-referrer-when-downgrade': ''
},
'https-to-https': {
'no-referrer': '',
'unsafe-url': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=unsafe-url',
'origin': 'https://example.com',
'origin-when-cross-origin': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=origin-when-cross-origin',
'no-referrer-when-downgrade': 'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer-when-downgrade'
}
}
};
function runit() {
var url = 'bug704320.sjs?action=get-test-results';
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = function() {
//dump("\n\n >>>>>>>>>>> XHR ReadyState change \n" + url + "\n\n\n\n");
if (this.readyState == 4) {
document.getElementById('content').textContent +=
JSON.stringify(JSON.parse(this.response), null, 4);
//dump("\n\n >>>>>>>>>>> GOT RESPONSE: \n" + this.response + "\n\n\n\n");
var response = JSON.parse(this.response);
for (type in response) {
for (scheme in response[type]) {
for (policy in response[type][scheme]) {
var expectedResult = expectedResults[type] === undefined ?
expectedResults['default'][scheme][policy] :
expectedResults[type][scheme][policy];
is(response[type][scheme][policy], expectedResult,
type + ' ' + scheme + ' ' + policy);
}
}
}
SimpleTest.finish();
}
};
xhr.send();
}
// BEGIN
// Currently triggers assertions on e10s due to bug 820466. If you try to run
// this on e10s, you'll get some ssl-related assertions and should add this line:
// SimpleTest.expectAssertions(0,15);
// But this test is disabled on e10s for unexpected failures. See bug 1100362.
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
'set': [['security.mixed_content.block_active_content', false],
['security.mixed_content.block_display_content', false],
['browser.send_pings', true],
['browser.send_pings.max_per_link', 1],
['browser.send_pings.require_same_host', false]]
},
function() {
var testContainer = document.getElementById('test');
testIframeUrls.forEach(function(url) {
var iframe = document.createElement('iframe');
iframe.setAttribute('class', 'test');
iframe.src = url;
testContainer.appendChild(iframe);
});
var numFrames = testIframeUrls.length;
var numFramesReady = 0;
window.addEventListener('message', function(event) {
++numFramesReady;
if (numFramesReady >= numFrames) {
runit();
}
}, false);
});
</script>
</body>
</html>

View File

@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=704320
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 704320 - HTTP to HTTP</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="referrerHelper.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.7">
var testIframeUrls = [
// HTTP to HTTP
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer-when-downgrade',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=no-referrer',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=unsafe-url',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=origin',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=http&policy=origin-when-cross-origin',
];
SimpleTest.waitForExplicitFinish();
var advance = function() { tests.next(); };
/**
* This is the main test routine -- serialized by use of a generator.
* It performs all tests in sequence using in the same iframe.
*/
var tests = (function() {
var iframe = document.getElementById("testframe");
iframe.onload = function() {
advance();
}
// load the test frame from testIframeUrls[url]
// it will call back into this function via postMessage when it finishes loading.
// and continue beyond the yield.
for(url in testIframeUrls) {
yield iframe.src = testIframeUrls[url];
// run test and check result for loaded test URL
yield checkExpectedGlobalResults();
}
// complete. Be sure to yield so we don't call this twice.
yield SimpleTest.finish();
})();
</script>
</head>
<body onload="tests.next();">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=704320">Mozilla Bug 704320 - HTTP to HTTP</a>
<p id="display"></p>
<pre id="content">
</pre>
<iframe id="testframe"></iframe>
</body>
</html>

View File

@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=704320
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 704320 - HTTP to HTTPS</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="referrerHelper.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.7">
var testIframeUrls = [
// HTTP to HTTPS
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer-when-downgrade',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=no-referrer',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=unsafe-url',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=origin',
'http://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=http&scheme-to=https&policy=origin-when-cross-origin',
];
SimpleTest.waitForExplicitFinish();
var advance = function() { tests.next(); };
/**
* This is the main test routine -- serialized by use of a generator.
* It performs all tests in sequence using in the same iframe.
*/
var tests = (function() {
var iframe = document.getElementById("testframe");
iframe.onload = function() {
advance();
}
// load the test frame from testIframeUrls[url]
// it will call back into this function via postMessage when it finishes loading.
// and continue beyond the yield.
for(url in testIframeUrls) {
yield iframe.src = testIframeUrls[url];
// run test and check result for loaded test URL
yield checkExpectedGlobalResults();
}
// complete. Be sure to yield so we don't call this twice.
yield SimpleTest.finish();
})();
</script>
</head>
<body onload="tests.next();">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=704320">Mozilla Bug 704320 - HTTP to HTTPS</a>
<p id="display"></p>
<pre id="content">
</pre>
<iframe id="testframe"></iframe>
</body>
</html>

View File

@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=704320
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 704320 - HTTPS to HTTP</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="referrerHelper.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.7">
var testIframeUrls = [
// HTTPS to HTTP
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=no-referrer-when-downgrade',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=no-referrer',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=unsafe-url',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=origin',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=http&policy=origin-when-cross-origin',
];
SimpleTest.waitForExplicitFinish();
var advance = function() { tests.next(); };
/**
* This is the main test routine -- serialized by use of a generator.
* It performs all tests in sequence using in the same iframe.
*/
var tests = (function() {
var iframe = document.getElementById("testframe");
iframe.onload = function() {
advance();
}
// load the test frame from testIframeUrls[url]
// it will call back into this function via postMessage when it finishes loading.
// and continue beyond the yield.
for(url in testIframeUrls) {
yield iframe.src = testIframeUrls[url];
// run test and check result for loaded test URL
yield checkExpectedGlobalResults();
}
// complete. Be sure to yield so we don't call this twice.
yield SimpleTest.finish();
})();
</script>
</head>
<body onload="tests.next();">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=704320">Mozilla Bug 704320 - HTTPS to HTTP</a>
<p id="display"></p>
<pre id="content">
</pre>
<iframe id="testframe"></iframe>
</body>
</html>

View File

@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=704320
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 704320 - HTTPS to HTTPS</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="referrerHelper.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.7">
var testIframeUrls = [
// HTTPS to HTTPS
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer-when-downgrade',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=no-referrer',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=unsafe-url',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=origin',
'https://example.com/tests/dom/base/test/bug704320.sjs?action=create-1st-level-iframe&scheme-from=https&scheme-to=https&policy=origin-when-cross-origin'
];
SimpleTest.waitForExplicitFinish();
var advance = function() { tests.next(); };
/**
* This is the main test routine -- serialized by use of a generator.
* It performs all tests in sequence using in the same iframe.
*/
var tests = (function() {
var iframe = document.getElementById("testframe");
iframe.onload = function() {
advance();
}
// load the test frame from testIframeUrls[url]
// it will call back into this function via postMessage when it finishes loading.
// and continue beyond the yield.
for(url in testIframeUrls) {
yield iframe.src = testIframeUrls[url];
// run test and check result for loaded test URL
yield checkExpectedGlobalResults();
}
// complete. Be sure to yield so we don't call this twice.
yield SimpleTest.finish();
})();
</script>
</head>
<body onload="tests.next();">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=704320">Mozilla Bug 704320 - HTTPS to HTTPS</a>
<p id="display"></p>
<pre id="content">
</pre>
<iframe id="testframe"></iframe>
</body>
</html>

View File

@ -37,7 +37,7 @@ var tests = (function() {
yield iframe.src = sjs + "&policy=" + escape('default'); yield iframe.src = sjs + "&policy=" + escape('default');
// check the first test (two images, no referrers) // check the first test (two images, no referrers)
yield checkResults("default", ["full"]); yield checkIndividualResults("default", ["full"]);
// check invalid policy // check invalid policy
// According to the spec section 6.4, if there is a policy token // According to the spec section 6.4, if there is a policy token
@ -45,7 +45,7 @@ var tests = (function() {
// should be the policy used. // should be the policy used.
yield resetCounter(); yield resetCounter();
yield iframe.src = sjs + "&policy=" + escape('invalid-policy'); yield iframe.src = sjs + "&policy=" + escape('invalid-policy');
yield checkResults("invalid", ["none"]); yield checkIndividualResults("invalid", ["none"]);
// whitespace checks. // whitespace checks.
// according to the spec section 4.1, the content attribute's value // according to the spec section 4.1, the content attribute's value
@ -53,20 +53,20 @@ var tests = (function() {
// trailing whitespace. // trailing whitespace.
yield resetCounter(); yield resetCounter();
yield iframe.src = sjs + "&policy=" + escape('default '); yield iframe.src = sjs + "&policy=" + escape('default ');
yield checkResults("trailing whitespace", ["full"]); yield checkIndividualResults("trailing whitespace", ["full"]);
yield resetCounter(); yield resetCounter();
yield iframe.src = sjs + "&policy=" + escape(' origin\f'); yield iframe.src = sjs + "&policy=" + escape(' origin\f');
yield checkResults("trailing form feed", ["origin"]); yield checkIndividualResults("trailing form feed", ["origin"]);
yield resetCounter(); yield resetCounter();
yield iframe.src = sjs + "&policy=" + escape('\f origin'); yield iframe.src = sjs + "&policy=" + escape('\f origin');
yield checkResults("leading form feed", ["origin"]); yield checkIndividualResults("leading form feed", ["origin"]);
// origin when cross-origin (trimming whitespace) // origin when cross-origin (trimming whitespace)
yield resetCounter(); yield resetCounter();
yield iframe.src = sjs + "&policy=" + escape(' origin-when-cross-origin'); yield iframe.src = sjs + "&policy=" + escape(' origin-when-cross-origin');
yield checkResults("origin-when-cross-origin", ["origin", "full"]); yield checkIndividualResults("origin-when-cross-origin", ["origin", "full"]);
// according to the spec section 4.1: // according to the spec section 4.1:
// "If the meta element lacks a content attribute, or if that attributes // "If the meta element lacks a content attribute, or if that attributes
@ -77,16 +77,16 @@ var tests = (function() {
// http://www.w3.org/html/wg/drafts/html/CR/infrastructure.html#space-character // http://www.w3.org/html/wg/drafts/html/CR/infrastructure.html#space-character
yield resetCounter(); yield resetCounter();
yield iframe.src = sjs + "&policy=" + escape(' \t '); yield iframe.src = sjs + "&policy=" + escape(' \t ');
yield checkResults("basic whitespace only policy", ["full"]); yield checkIndividualResults("basic whitespace only policy", ["full"]);
yield resetCounter(); yield resetCounter();
yield iframe.src = sjs + "&policy=" + escape(' \f\r\n\t '); yield iframe.src = sjs + "&policy=" + escape(' \f\r\n\t ');
yield checkResults("whitespace only policy", ["full"]); yield checkIndividualResults("whitespace only policy", ["full"]);
// and double-check that no-referrer works. // and double-check that no-referrer works.
yield resetCounter(); yield resetCounter();
yield iframe.src = sjs + "&policy=" + escape('no-referrer'); yield iframe.src = sjs + "&policy=" + escape('no-referrer');
yield checkResults("no-referrer", ["none"]); yield checkIndividualResults("no-referrer", ["none"]);
// complete. Be sure to yield so we don't call this twice. // complete. Be sure to yield so we don't call this twice.
yield SimpleTest.finish(); yield SimpleTest.finish();

View File

@ -9,6 +9,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=704320
<meta charset="utf-8"> <meta charset="utf-8">
<title>Test preloads for Bug 704320</title> <title>Test preloads for Bug 704320</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="referrerHelper.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript;version=1.7"> <script type="application/javascript;version=1.7">
@ -16,24 +17,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=704320
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
var advance = function() { tests.next(); }; var advance = function() { tests.next(); };
/**
* Listen for notifications from the child.
* These are sent in case of error, or when the loads we await have completed.
*/
window.addEventListener("message", function(event) {
if (event.data == "childLoadComplete") {
// all three loads happen, continue the test.
advance();
} else if (event.data == "childOverload") {
// too many loads happened in a test frame, abort.
ok(false, "Too many load handlers called in test.");
SimpleTest.finish();
} else if (event.data.indexOf("fail-") == 0) {
// something else failed in the test frame, abort.
ok(false, "Child failed the test with error " + event.data.substr(5));
SimpleTest.finish();
}});
/** /**
* This is the main test routine -- serialized by use of a generator. * This is the main test routine -- serialized by use of a generator.
* It resets the counter, then performs two tests in sequence using * It resets the counter, then performs two tests in sequence using
@ -138,33 +121,6 @@ function finalizePreloadReuse(results) {
advance(); advance();
} }
/**
* helper to perform an XHR.
* Used by resetCounter() and checkResults().
*/
function doXHR(url, onSuccess, onFail) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.status == 200) {
onSuccess(xhr);
} else {
onFail(xhr);
}
};
xhr.open('GET', url, true);
xhr.send(null);
}
/**
* This triggers state-resetting on the counter server.
*/
function resetCounter() {
doXHR('/tests/dom/base/test/bug704320_counter.sjs?reset',
advance,
function(xhr) {
ok(false, "Need to be able to reset the request counter");
});
}
/** /**
* Grabs the results via XHR and passes to checker. * Grabs the results via XHR and passes to checker.

View File

@ -102,27 +102,21 @@
addEventListener("load", function() { addEventListener("load", function() {
var principal = SpecialPowers.wrap(document).nodePrincipal; var principal = SpecialPowers.wrap(document).nodePrincipal;
SpecialPowers.addPermission("browser", true, { url: SpecialPowers.wrap(principal.URI).spec, SpecialPowers.pushPermissions([
appId: principal.appId, { "type": "browser", "allow": 1, "context": { "url": principal.URI.spec,
isInBrowserElement: false }); "appId": principal.appId,
SpecialPowers.addPermission("browser", true, { url: SpecialPowers.wrap(principal.URI).spec, "isInBrowserElement": false }},
appId: principal.appId, { "type": "browser", "allow": 1, "context": { "url": principal.URI.spec,
isInBrowserElement: true }); "appId": principal.appId,
SpecialPowers.pushPrefEnv({ "isInBrowserElement": true }}
"set": [ ], () => {
["dom.mozBrowserFramesEnabled", true], SpecialPowers.pushPrefEnv({
["dom.ipc.browser_frames.oop_by_default", false], "set": [
] ["dom.mozBrowserFramesEnabled", true],
}, runTests); ["dom.ipc.browser_frames.oop_by_default", false],
}); ]
SimpleTest.registerCleanupFunction(function () { }, runTests);
var principal = SpecialPowers.wrap(document).nodePrincipal; });
SpecialPowers.removePermission("browser", { url: SpecialPowers.wrap(principal.URI).spec,
appId: principal.appId,
isInBrowserElement: false });
SpecialPowers.removePermission("browser", { url: SpecialPowers.wrap(principal.URI).spec,
appId: principal.appId,
isInBrowserElement: true });
}); });
</script> </script>
</body> </body>

View File

@ -7,6 +7,7 @@
<title>Test for Bug 782751</title> <title>Test for Bug 782751</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="test_performance_user_timing.js"></script>
</head> </head>
<body> <body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=782751">Mozilla Bug 782751 - User Timing API</a> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=782751">Mozilla Bug 782751 - User Timing API</a>
@ -15,271 +16,6 @@
<pre id="test"> <pre id="test">
<script class="testbody" type="text/javascript"> <script class="testbody" type="text/javascript">
var index = 0; var index = 0;
var steps = [
// Test single mark addition
function () {
ok(true, "Running mark addition test");
performance.mark("test");
var marks = performance.getEntriesByType("mark");
is(marks.length, 1, "Number of marks should be 1");
var mark = marks[0];
is(mark.name, "test", "mark name should be 'test'");
is(mark.entryType, "mark", "mark type should be 'mark'");
isnot(mark.startTime, 0, "mark start time should not be 0");
is(mark.duration, 0, "mark duration should be 0");
},
// Test multiple mark addition
function () {
ok(true, "Running multiple mark with same name addition test");
performance.mark("test");
performance.mark("test");
performance.mark("test");
var marks_type = performance.getEntriesByType("mark");
is(marks_type.length, 3, "Number of marks by type should be 3");
var marks_name = performance.getEntriesByName("test");
is(marks_name.length, 3, "Number of marks by name should be 3");
var mark = marks_name[0];
is(mark.name, "test", "mark name should be 'test'");
is(mark.entryType, "mark", "mark type should be 'mark'");
isnot(mark.startTime, 0, "mark start time should not be 0");
is(mark.duration, 0, "mark duration should be 0");
var times = [];
// This also tests the chronological ordering specified as
// required for getEntries in the performance timeline spec.
marks_name.forEach(function(s) {
times.forEach(function(time) {
ok(s.startTime >= time.startTime,
"Times should be equal or increasing between similarly named marks: " + s.startTime + " >= " + time.startTime);
});
times.push(s);
});
},
// Test all marks removal
function () {
ok(true, "Running all mark removal test");
performance.mark("test");
performance.mark("test2");
var marks = performance.getEntriesByType("mark");
is(marks.length, 2, "number of marks before all removal");
performance.clearMarks();
marks = performance.getEntriesByType("mark");
is(marks.length, 0, "number of marks after all removal");
},
// Test single mark removal
function () {
ok(true, "Running removal test (0 'test' marks with other marks)");
performance.mark("test2");
var marks = performance.getEntriesByType("mark");
is(marks.length, 1, "number of marks before all removal");
performance.clearMarks("test");
marks = performance.getEntriesByType("mark");
is(marks.length, 1, "number of marks after all removal");
},
// Test single mark removal
function () {
ok(true, "Running removal test (0 'test' marks with no other marks)");
var marks = performance.getEntriesByType("mark");
is(marks.length, 0, "number of marks before all removal");
performance.clearMarks("test");
marks = performance.getEntriesByType("mark");
is(marks.length, 0, "number of marks after all removal");
},
function () {
ok(true, "Running removal test (1 'test' mark with other marks)");
performance.mark("test");
performance.mark("test2");
var marks = performance.getEntriesByType("mark");
is(marks.length, 2, "number of marks before all removal");
performance.clearMarks("test");
marks = performance.getEntriesByType("mark");
is(marks.length, 1, "number of marks after all removal");
},
function () {
ok(true, "Running removal test (1 'test' mark with no other marks)");
performance.mark("test");
var marks = performance.getEntriesByType("mark");
is(marks.length, 1, "number of marks before all removal");
performance.clearMarks("test");
marks = performance.getEntriesByType("mark");
is(marks.length, 0, "number of marks after all removal");
},
function () {
ok(true, "Running removal test (2 'test' marks with other marks)");
performance.mark("test");
performance.mark("test");
performance.mark("test2");
var marks = performance.getEntriesByType("mark");
is(marks.length, 3, "number of marks before all removal");
performance.clearMarks("test");
marks = performance.getEntriesByType("mark");
is(marks.length, 1, "number of marks after all removal");
},
function () {
ok(true, "Running removal test (2 'test' marks with no other marks)");
performance.mark("test");
performance.mark("test");
var marks = performance.getEntriesByType("mark");
is(marks.length, 2, "number of marks before all removal");
performance.clearMarks("test");
marks = performance.getEntriesByType("mark");
is(marks.length, 0, "number of marks after all removal");
},
// Test mark name being same as navigation timing parameter
function () {
ok(true, "Running mark name collision test");
for (n in performance.timing) {
try {
if (n == "toJSON") {
ok(true, "Skipping toJSON entry in collision test");
continue;
}
performance.mark(n);
ok(false, "Mark name collision test failed for name " + n + ", shouldn't make it here!");
} catch (e) {
ok(e instanceof DOMException, "DOM exception thrown for mark named " + n);
is(e.code, e.SYNTAX_ERR, "DOM exception for name collision is syntax error");
}
};
},
// Test measure
function () {
ok(true, "Running measure addition with no start/end time test");
performance.measure("test");
var measures = performance.getEntriesByType("measure");
is(measures.length, 1, "number of measures should be 1");
var measure = measures[0];
is(measure.name, "test", "measure name should be 'test'");
is(measure.entryType, "measure", "measure type should be 'measure'");
is(measure.startTime, 0, "measure start time should be zero");
ok(measure.duration >= 0, "measure duration should not be negative");
},
function () {
ok(true, "Running measure addition with only start time test");
performance.mark("test1");
performance.measure("test", "test1", undefined);
var measures = performance.getEntriesByName("test", "measure");
var marks = performance.getEntriesByName("test1", "mark");
var measure = measures[0];
var mark = marks[0];
is(measure.startTime, mark.startTime, "measure start time should be equal to the mark startTime");
ok(measure.duration >= 0, "measure duration should not be negative");
},
function () {
ok(true, "Running measure addition with only end time test");
performance.mark("test1");
performance.measure("test", undefined, "test1");
var measures = performance.getEntriesByName("test", "measure");
var marks = performance.getEntriesByName("test1", "mark");
var measure = measures[0];
var mark = marks[0];
ok(measure.duration >= 0, "measure duration should not be negative");
},
// Test measure picking latest version of similarly named tags
function () {
ok(true, "Running multiple mark with same name addition test");
performance.mark("test");
performance.mark("test");
performance.mark("test");
performance.mark("test2");
var marks_name = performance.getEntriesByName("test");
is(marks_name.length, 3, "Number of marks by name should be 3");
var marks_name2 = performance.getEntriesByName("test2");
is(marks_name2.length, 1, "Number of marks by name should be 1");
var test_mark = marks_name2[0];
performance.measure("test", "test", "test2");
var measures_type = performance.getEntriesByType("measure");
var last_mark = marks_name[marks_name.length - 1];
is(measures_type.length, 1, "Number of measures by type should be 1");
var measure = measures_type[0];
is(measure.startTime, last_mark.startTime, "Measure start time should be the start time of the latest 'test' mark");
// Tolerance testing to avoid oranges, since we're doing double math across two different languages.
ok(measure.duration - (test_mark.startTime - last_mark.startTime) < .00001,
"Measure duration ( " + measure.duration + ") should be difference between two marks");
},
// Test all measure removal
function () {
ok(true, "Running all measure removal test");
performance.measure("test");
performance.measure("test2");
var measures = performance.getEntriesByType("measure");
is(measures.length, 2, "measure entries should be length 2");
performance.clearMeasures();
measures = performance.getEntriesByType("measure");
is(measures.length, 0, "measure entries should be length 0");
},
// Test single measure removal
function () {
ok(true, "Running all measure removal test");
performance.measure("test");
performance.measure("test2");
var measures = performance.getEntriesByType("measure");
is(measures.length, 2, "measure entries should be length 2");
performance.clearMeasures("test");
measures = performance.getEntriesByType("measure");
is(measures.length, 1, "measure entries should be length 1");
},
// Test measure with invalid start time mark name
function () {
ok(true, "Running measure invalid start test");
try {
performance.measure("test", "notamark");
ok(false, "invalid measure start time exception not thrown!");
} catch (e) {
ok(e instanceof DOMException, "DOM exception thrown for invalid measure");
is(e.code, e.SYNTAX_ERR, "DOM exception for invalid time is syntax error");
}
},
// Test measure with invalid end time mark name
function () {
ok(true, "Running measure invalid end test");
try {
performance.measure("test", undefined, "notamark");
ok(false, "invalid measure end time exception not thrown!");
} catch (e) {
ok(e instanceof DOMException, "DOM exception thrown for invalid measure");
is(e.code, e.SYNTAX_ERR, "DOM exception for invalid time is syntax error");
}
},
// Test measure name being same as navigation timing parameter
function () {
ok(true, "Running measure name collision test");
for (n in performance.timing) {
try {
if (n == "toJSON") {
ok(true, "Skipping toJSON entry in collision test");
continue;
}
performance.measure(n);
ok(false, "Measure name collision test failed for name " + n + ", shouldn't make it here!");
} catch (e) {
ok(e instanceof DOMException, "DOM exception thrown for measure named " + n);
is(e.code, e.SYNTAX_ERR, "DOM exception for name collision is syntax error");
}
};
},
// Test measure mark being a reserved name
function () {
ok(true, "Create measures using all reserved names");
for (n in performance.timing) {
try {
if (n == "toJSON") {
ok(true, "Skipping toJSON entry in collision test");
continue;
}
performance.measure("test", n);
ok(true, "Measure created from reserved name as starting time: " + n);
} catch (e) {
ok(["redirectStart", "redirectEnd", "unloadEventStart", "unloadEventEnd", "loadEventEnd"].indexOf(n) >= 0,
"Measure created from reserved name as starting time: " + n + " and threw expected error");
}
};
},
function () {
ok(true, "all done!");
SimpleTest.finish();
}
// TODO: Test measure picking latest version of similarly named tags
];
function next() { function next() {
ok(true, "Begin!"); ok(true, "Begin!");
@ -296,6 +32,8 @@
ok(false, "Caught exception", ex); ok(false, "Caught exception", ex);
} }
} }
SimpleTest.finish();
} }
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();

View File

@ -0,0 +1,272 @@
var steps = [
// Test single mark addition
function () {
ok(true, "Running mark addition test");
performance.mark("test");
var marks = performance.getEntriesByType("mark");
is(marks.length, 1, "Number of marks should be 1");
var mark = marks[0];
is(mark.name, "test", "mark name should be 'test'");
is(mark.entryType, "mark", "mark type should be 'mark'");
isnot(mark.startTime, 0, "mark start time should not be 0");
is(mark.duration, 0, "mark duration should be 0");
},
// Test multiple mark addition
function () {
ok(true, "Running multiple mark with same name addition test");
performance.mark("test");
performance.mark("test");
performance.mark("test");
var marks_type = performance.getEntriesByType("mark");
is(marks_type.length, 3, "Number of marks by type should be 3");
var marks_name = performance.getEntriesByName("test");
is(marks_name.length, 3, "Number of marks by name should be 3");
var mark = marks_name[0];
is(mark.name, "test", "mark name should be 'test'");
is(mark.entryType, "mark", "mark type should be 'mark'");
isnot(mark.startTime, 0, "mark start time should not be 0");
is(mark.duration, 0, "mark duration should be 0");
var times = [];
// This also tests the chronological ordering specified as
// required for getEntries in the performance timeline spec.
marks_name.forEach(function(s) {
times.forEach(function(time) {
ok(s.startTime >= time.startTime,
"Times should be equal or increasing between similarly named marks: " + s.startTime + " >= " + time.startTime);
});
times.push(s);
});
},
// Test all marks removal
function () {
ok(true, "Running all mark removal test");
performance.mark("test");
performance.mark("test2");
var marks = performance.getEntriesByType("mark");
is(marks.length, 2, "number of marks before all removal");
performance.clearMarks();
marks = performance.getEntriesByType("mark");
is(marks.length, 0, "number of marks after all removal");
},
// Test single mark removal
function () {
ok(true, "Running removal test (0 'test' marks with other marks)");
performance.mark("test2");
var marks = performance.getEntriesByType("mark");
is(marks.length, 1, "number of marks before all removal");
performance.clearMarks("test");
marks = performance.getEntriesByType("mark");
is(marks.length, 1, "number of marks after all removal");
},
// Test single mark removal
function () {
ok(true, "Running removal test (0 'test' marks with no other marks)");
var marks = performance.getEntriesByType("mark");
is(marks.length, 0, "number of marks before all removal");
performance.clearMarks("test");
marks = performance.getEntriesByType("mark");
is(marks.length, 0, "number of marks after all removal");
},
function () {
ok(true, "Running removal test (1 'test' mark with other marks)");
performance.mark("test");
performance.mark("test2");
var marks = performance.getEntriesByType("mark");
is(marks.length, 2, "number of marks before all removal");
performance.clearMarks("test");
marks = performance.getEntriesByType("mark");
is(marks.length, 1, "number of marks after all removal");
},
function () {
ok(true, "Running removal test (1 'test' mark with no other marks)");
performance.mark("test");
var marks = performance.getEntriesByType("mark");
is(marks.length, 1, "number of marks before all removal");
performance.clearMarks("test");
marks = performance.getEntriesByType("mark");
is(marks.length, 0, "number of marks after all removal");
},
function () {
ok(true, "Running removal test (2 'test' marks with other marks)");
performance.mark("test");
performance.mark("test");
performance.mark("test2");
var marks = performance.getEntriesByType("mark");
is(marks.length, 3, "number of marks before all removal");
performance.clearMarks("test");
marks = performance.getEntriesByType("mark");
is(marks.length, 1, "number of marks after all removal");
},
function () {
ok(true, "Running removal test (2 'test' marks with no other marks)");
performance.mark("test");
performance.mark("test");
var marks = performance.getEntriesByType("mark");
is(marks.length, 2, "number of marks before all removal");
performance.clearMarks("test");
marks = performance.getEntriesByType("mark");
is(marks.length, 0, "number of marks after all removal");
},
// Test mark name being same as navigation timing parameter
function () {
ok(true, "Running mark name collision test");
for (n in performance.timing) {
try {
if (n == "toJSON") {
ok(true, "Skipping toJSON entry in collision test");
continue;
}
performance.mark(n);
ok(false, "Mark name collision test failed for name " + n + ", shouldn't make it here!");
} catch (e) {
ok(e instanceof DOMException, "DOM exception thrown for mark named " + n);
is(e.code, e.SYNTAX_ERR, "DOM exception for name collision is syntax error");
}
};
},
// Test measure
function () {
ok(true, "Running measure addition with no start/end time test");
performance.measure("test");
var measures = performance.getEntriesByType("measure");
is(measures.length, 1, "number of measures should be 1");
var measure = measures[0];
is(measure.name, "test", "measure name should be 'test'");
is(measure.entryType, "measure", "measure type should be 'measure'");
is(measure.startTime, 0, "measure start time should be zero");
ok(measure.duration >= 0, "measure duration should not be negative");
},
function () {
ok(true, "Running measure addition with only start time test");
performance.mark("test1");
performance.measure("test", "test1", undefined);
var measures = performance.getEntriesByName("test", "measure");
var marks = performance.getEntriesByName("test1", "mark");
var measure = measures[0];
var mark = marks[0];
is(measure.startTime, mark.startTime, "measure start time should be equal to the mark startTime");
ok(measure.duration >= 0, "measure duration should not be negative");
},
function () {
ok(true, "Running measure addition with only end time test");
performance.mark("test1");
performance.measure("test", undefined, "test1");
var measures = performance.getEntriesByName("test", "measure");
var marks = performance.getEntriesByName("test1", "mark");
var measure = measures[0];
var mark = marks[0];
ok(measure.duration >= 0, "measure duration should not be negative");
},
// Test measure picking latest version of similarly named tags
function () {
ok(true, "Running multiple mark with same name addition test");
performance.mark("test");
performance.mark("test");
performance.mark("test");
performance.mark("test2");
var marks_name = performance.getEntriesByName("test");
is(marks_name.length, 3, "Number of marks by name should be 3");
var marks_name2 = performance.getEntriesByName("test2");
is(marks_name2.length, 1, "Number of marks by name should be 1");
var test_mark = marks_name2[0];
performance.measure("test", "test", "test2");
var measures_type = performance.getEntriesByType("measure");
var last_mark = marks_name[marks_name.length - 1];
is(measures_type.length, 1, "Number of measures by type should be 1");
var measure = measures_type[0];
is(measure.startTime, last_mark.startTime, "Measure start time should be the start time of the latest 'test' mark");
// Tolerance testing to avoid oranges, since we're doing double math across two different languages.
ok(measure.duration - (test_mark.startTime - last_mark.startTime) < .00001,
"Measure duration ( " + measure.duration + ") should be difference between two marks");
},
function() {
ok(true, "Running measure addition with no start/end time test");
performance.measure("test", "navigationStart");
var measures = performance.getEntriesByType("measure");
is(measures.length, 1, "number of measures should be 1");
var measure = measures[0];
is(measure.name, "test", "measure name should be 'test'");
is(measure.entryType, "measure", "measure type should be 'measure'");
is(measure.startTime, 0, "measure start time should be zero");
ok(measure.duration >= 0, "measure duration should not be negative");
},
// Test all measure removal
function () {
ok(true, "Running all measure removal test");
performance.measure("test");
performance.measure("test2");
var measures = performance.getEntriesByType("measure");
is(measures.length, 2, "measure entries should be length 2");
performance.clearMeasures();
measures = performance.getEntriesByType("measure");
is(measures.length, 0, "measure entries should be length 0");
},
// Test single measure removal
function () {
ok(true, "Running all measure removal test");
performance.measure("test");
performance.measure("test2");
var measures = performance.getEntriesByType("measure");
is(measures.length, 2, "measure entries should be length 2");
performance.clearMeasures("test");
measures = performance.getEntriesByType("measure");
is(measures.length, 1, "measure entries should be length 1");
},
// Test measure with invalid start time mark name
function () {
ok(true, "Running measure invalid start test");
try {
performance.measure("test", "notamark");
ok(false, "invalid measure start time exception not thrown!");
} catch (e) {
ok(e instanceof DOMException, "DOM exception thrown for invalid measure");
is(e.code, e.SYNTAX_ERR, "DOM exception for invalid time is syntax error");
}
},
// Test measure with invalid end time mark name
function () {
ok(true, "Running measure invalid end test");
try {
performance.measure("test", undefined, "notamark");
ok(false, "invalid measure end time exception not thrown!");
} catch (e) {
ok(e instanceof DOMException, "DOM exception thrown for invalid measure");
is(e.code, e.SYNTAX_ERR, "DOM exception for invalid time is syntax error");
}
},
// Test measure name being same as navigation timing parameter
function () {
ok(true, "Running measure name collision test");
for (n in performance.timing) {
try {
if (n == "toJSON") {
ok(true, "Skipping toJSON entry in collision test");
continue;
}
performance.measure(n);
ok(false, "Measure name collision test failed for name " + n + ", shouldn't make it here!");
} catch (e) {
ok(e instanceof DOMException, "DOM exception thrown for measure named " + n);
is(e.code, e.SYNTAX_ERR, "DOM exception for name collision is syntax error");
}
};
},
// Test measure mark being a reserved name
function () {
ok(true, "Create measures using all reserved names");
for (n in performance.timing) {
try {
if (n == "toJSON") {
ok(true, "Skipping toJSON entry in collision test");
continue;
}
performance.measure("test", n);
ok(true, "Measure created from reserved name as starting time: " + n);
} catch (e) {
ok(["redirectStart", "redirectEnd", "unloadEventStart", "unloadEventEnd", "loadEventEnd"].indexOf(n) >= 0,
"Measure created from reserved name as starting time: " + n + " and threw expected error");
}
};
},
// TODO: Test measure picking latest version of similarly named tags
];

View File

@ -2642,7 +2642,8 @@ CanvasRenderingContext2D::Stroke(const CanvasPath& path)
Redraw(); Redraw();
} }
void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement) void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement,
ErrorResult& aRv)
{ {
EnsureUserSpacePath(); EnsureUserSpacePath();
@ -2674,8 +2675,12 @@ void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement
// set dashing for foreground // set dashing for foreground
FallibleTArray<mozilla::gfx::Float>& dash = CurrentState().dash; FallibleTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
dash.AppendElement(1); for (uint32_t i = 0; i < 2; ++i) {
dash.AppendElement(1); if (!dash.AppendElement(1)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
}
// set the foreground focus color // set the foreground focus color
CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0,0,0, 255)); CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0,0,0, 255));
@ -3947,7 +3952,8 @@ CanvasRenderingContext2D::SetMozDashOffset(double mozDashOffset)
} }
void void
CanvasRenderingContext2D::SetLineDash(const Sequence<double>& aSegments) CanvasRenderingContext2D::SetLineDash(const Sequence<double>& aSegments,
ErrorResult& aRv)
{ {
FallibleTArray<mozilla::gfx::Float> dash; FallibleTArray<mozilla::gfx::Float> dash;
@ -3957,11 +3963,18 @@ CanvasRenderingContext2D::SetLineDash(const Sequence<double>& aSegments)
// taken care of by WebIDL // taken care of by WebIDL
return; return;
} }
dash.AppendElement(aSegments[x]);
if (!dash.AppendElement(aSegments[x])) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
} }
if (aSegments.Length() % 2) { // If the number of elements is odd, concatenate again if (aSegments.Length() % 2) { // If the number of elements is odd, concatenate again
for (uint32_t x = 0; x < aSegments.Length(); x++) { for (uint32_t x = 0; x < aSegments.Length(); x++) {
dash.AppendElement(aSegments[x]); if (!dash.AppendElement(aSegments[x])) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
} }
} }

View File

@ -187,7 +187,7 @@ public:
void Fill(const CanvasPath& path, const CanvasWindingRule& winding); void Fill(const CanvasPath& path, const CanvasWindingRule& winding);
void Stroke(); void Stroke();
void Stroke(const CanvasPath& path); void Stroke(const CanvasPath& path);
void DrawFocusIfNeeded(mozilla::dom::Element& element); void DrawFocusIfNeeded(mozilla::dom::Element& element, ErrorResult& aRv);
bool DrawCustomFocusRing(mozilla::dom::Element& element); bool DrawCustomFocusRing(mozilla::dom::Element& element);
void Clip(const CanvasWindingRule& winding); void Clip(const CanvasWindingRule& winding);
void Clip(const CanvasPath& path, const CanvasWindingRule& winding); void Clip(const CanvasPath& path, const CanvasWindingRule& winding);
@ -363,7 +363,8 @@ public:
void SetMozDash(JSContext* cx, const JS::Value& mozDash, void SetMozDash(JSContext* cx, const JS::Value& mozDash,
mozilla::ErrorResult& error); mozilla::ErrorResult& error);
void SetLineDash(const Sequence<double>& mSegments); void SetLineDash(const Sequence<double>& mSegments,
mozilla::ErrorResult& aRv);
void GetLineDash(nsTArray<double>& mSegments) const; void GetLineDash(nsTArray<double>& mSegments) const;
void SetLineDashOffset(double mOffset); void SetLineDashOffset(double mOffset);

View File

@ -10,6 +10,7 @@
namespace mozilla { namespace mozilla {
class ErrorResult;
class WebGLSampler; class WebGLSampler;
class WebGLSync; class WebGLSync;
class WebGLTransformFeedback; class WebGLTransformFeedback;
@ -56,9 +57,10 @@ public:
GLbitfield mask, GLenum filter); GLbitfield mask, GLenum filter);
void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);
void GetInternalformatParameter(JSContext*, GLenum target, GLenum internalformat, GLenum pname, JS::MutableHandleValue retval); void GetInternalformatParameter(JSContext*, GLenum target, GLenum internalformat, GLenum pname, JS::MutableHandleValue retval);
void InvalidateFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments); void InvalidateFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments,
ErrorResult& aRv);
void InvalidateSubFramebuffer (GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y, void InvalidateSubFramebuffer (GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y,
GLsizei width, GLsizei height); GLsizei width, GLsizei height, ErrorResult& aRv);
void ReadBuffer(GLenum mode); void ReadBuffer(GLenum mode);
void RenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, void RenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat,
GLsizei width, GLsizei height); GLsizei width, GLsizei height);

View File

@ -343,26 +343,38 @@ WebGL2Context::GetInternalformatParameter(JSContext*, GLenum target, GLenum inte
// Map attachments intended for the default buffer, to attachments for a non- // Map attachments intended for the default buffer, to attachments for a non-
// default buffer. // default buffer.
static void static bool
TranslateDefaultAttachments(const dom::Sequence<GLenum>& in, dom::Sequence<GLenum>* out) TranslateDefaultAttachments(const dom::Sequence<GLenum>& in, dom::Sequence<GLenum>* out)
{ {
for (size_t i = 0; i < in.Length(); i++) { for (size_t i = 0; i < in.Length(); i++) {
switch (in[i]) { switch (in[i]) {
case LOCAL_GL_COLOR: case LOCAL_GL_COLOR:
out->AppendElement(LOCAL_GL_COLOR_ATTACHMENT0); if (!out->AppendElement(LOCAL_GL_COLOR_ATTACHMENT0)) {
return false;
}
break; break;
case LOCAL_GL_DEPTH: case LOCAL_GL_DEPTH:
out->AppendElement(LOCAL_GL_DEPTH_ATTACHMENT); if (!out->AppendElement(LOCAL_GL_DEPTH_ATTACHMENT)) {
return false;
}
break; break;
case LOCAL_GL_STENCIL: case LOCAL_GL_STENCIL:
out->AppendElement(LOCAL_GL_STENCIL_ATTACHMENT); if (!out->AppendElement(LOCAL_GL_STENCIL_ATTACHMENT)) {
return false;
}
break; break;
} }
} }
return true;
} }
void void
WebGL2Context::InvalidateFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments) WebGL2Context::InvalidateFramebuffer(GLenum target,
const dom::Sequence<GLenum>& attachments,
ErrorResult& aRv)
{ {
if (IsContextLost()) if (IsContextLost())
return; return;
@ -407,7 +419,11 @@ WebGL2Context::InvalidateFramebuffer(GLenum target, const dom::Sequence<GLenum>&
if (!fb && !isDefaultFB) { if (!fb && !isDefaultFB) {
dom::Sequence<GLenum> tmpAttachments; dom::Sequence<GLenum> tmpAttachments;
TranslateDefaultAttachments(attachments, &tmpAttachments); if (!TranslateDefaultAttachments(attachments, &tmpAttachments)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
gl->fInvalidateFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements()); gl->fInvalidateFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements());
} else { } else {
gl->fInvalidateFramebuffer(target, attachments.Length(), attachments.Elements()); gl->fInvalidateFramebuffer(target, attachments.Length(), attachments.Elements());
@ -416,7 +432,8 @@ WebGL2Context::InvalidateFramebuffer(GLenum target, const dom::Sequence<GLenum>&
void void
WebGL2Context::InvalidateSubFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments, WebGL2Context::InvalidateSubFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments,
GLint x, GLint y, GLsizei width, GLsizei height) GLint x, GLint y, GLsizei width, GLsizei height,
ErrorResult& aRv)
{ {
if (IsContextLost()) if (IsContextLost())
return; return;
@ -461,7 +478,11 @@ WebGL2Context::InvalidateSubFramebuffer(GLenum target, const dom::Sequence<GLenu
if (!fb && !isDefaultFB) { if (!fb && !isDefaultFB) {
dom::Sequence<GLenum> tmpAttachments; dom::Sequence<GLenum> tmpAttachments;
TranslateDefaultAttachments(attachments, &tmpAttachments); if (!TranslateDefaultAttachments(attachments, &tmpAttachments)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
gl->fInvalidateSubFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements(), gl->fInvalidateSubFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements(),
x, y, width, height); x, y, width, height);
} else { } else {

View File

@ -1962,7 +1962,9 @@ private:
if (!mKeyUsages.IsEmpty()) { if (!mKeyUsages.IsEmpty()) {
mJwk.mKey_ops.Construct(); mJwk.mKey_ops.Construct();
mJwk.mKey_ops.Value().AppendElements(mKeyUsages); if (!mJwk.mKey_ops.Value().AppendElements(mKeyUsages)) {
return NS_ERROR_OUT_OF_MEMORY;
}
} }
return NS_OK; return NS_OK;

View File

@ -316,7 +316,9 @@ DataStoreDB::DatabaseOpened()
} }
StringOrStringSequence objectStores; StringOrStringSequence objectStores;
objectStores.RawSetAsStringSequence().AppendElements(mObjectStores); if (!objectStores.RawSetAsStringSequence().AppendElements(mObjectStores)) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsRefPtr<IDBTransaction> txn; nsRefPtr<IDBTransaction> txn;
error = mDatabase->Transaction(objectStores, error = mDatabase->Transaction(objectStores,

View File

@ -1358,7 +1358,9 @@ DataStoreService::CreateFirstRevisionId(uint32_t aAppId,
new FirstRevisionIdCallback(aAppId, aName, aManifestURL); new FirstRevisionIdCallback(aAppId, aName, aManifestURL);
Sequence<nsString> dbs; Sequence<nsString> dbs;
dbs.AppendElement(NS_LITERAL_STRING(DATASTOREDB_REVISION)); if (!dbs.AppendElement(NS_LITERAL_STRING(DATASTOREDB_REVISION))) {
return NS_ERROR_OUT_OF_MEMORY;
}
return db->Open(IDBTransactionMode::Readwrite, dbs, callback); return db->Open(IDBTransactionMode::Readwrite, dbs, callback);
} }

View File

@ -580,6 +580,15 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
} }
break; break;
} }
case NS_MOUSE_ENTER_WIDGET:
// In some cases on e10s NS_MOUSE_ENTER_WIDGET
// event was sent twice into child process of content.
// (From specific widget code (sending is not permanent) and
// from ESM::DispatchMouseOrPointerEvent (sending is permanent)).
// Flag mNoCrossProcessBoundaryForwarding helps to
// suppress sending accidental event from widget code.
aEvent->mFlags.mNoCrossProcessBoundaryForwarding = true;
break;
case NS_MOUSE_EXIT_WIDGET: case NS_MOUSE_EXIT_WIDGET:
// If this is a remote frame, we receive NS_MOUSE_EXIT_WIDGET from the parent // If this is a remote frame, we receive NS_MOUSE_EXIT_WIDGET from the parent
// the mouse exits our content. Since the parent may update the cursor // the mouse exits our content. Since the parent may update the cursor
@ -592,6 +601,10 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
ClearCachedWidgetCursor(mCurrentTarget); ClearCachedWidgetCursor(mCurrentTarget);
} }
// Flag helps to suppress double event sending into process of content.
// For more information see comment above, at NS_MOUSE_ENTER_WIDGET case.
aEvent->mFlags.mNoCrossProcessBoundaryForwarding = true;
// If the event is not a top-level window exit, then it's not // If the event is not a top-level window exit, then it's not
// really an exit --- we may have traversed widget boundaries but // really an exit --- we may have traversed widget boundaries but
// we're still in our toplevel window. // we're still in our toplevel window.
@ -1183,7 +1196,6 @@ CrossProcessSafeEvent(const WidgetEvent& aEvent)
case NS_CONTEXTMENU: case NS_CONTEXTMENU:
case NS_MOUSE_ENTER_WIDGET: case NS_MOUSE_ENTER_WIDGET:
case NS_MOUSE_EXIT_WIDGET: case NS_MOUSE_EXIT_WIDGET:
case NS_MOUSE_OVER:
return true; return true;
default: default:
return false; return false;

View File

@ -1860,7 +1860,11 @@ HTMLInputElement::SetValue(const nsAString& aValue, ErrorResult& aRv)
return; return;
} }
Sequence<nsString> list; Sequence<nsString> list;
list.AppendElement(aValue); if (!list.AppendElement(aValue)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
MozSetFileNameArray(list, aRv); MozSetFileNameArray(list, aRv);
return; return;
} }
@ -2441,7 +2445,9 @@ HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames, uint32_t aLen
Sequence<nsString> list; Sequence<nsString> list;
for (uint32_t i = 0; i < aLength; ++i) { for (uint32_t i = 0; i < aLength; ++i) {
list.AppendElement(nsDependentString(aFileNames[i])); if (!list.AppendElement(nsDependentString(aFileNames[i]))) {
return NS_ERROR_OUT_OF_MEMORY;
}
} }
ErrorResult rv; ErrorResult rv;
@ -2492,7 +2498,10 @@ HTMLInputElement::SetUserInput(const nsAString& aValue)
if (mType == NS_FORM_INPUT_FILE) if (mType == NS_FORM_INPUT_FILE)
{ {
Sequence<nsString> list; Sequence<nsString> list;
list.AppendElement(aValue); if (!list.AppendElement(aValue)) {
return NS_ERROR_OUT_OF_MEMORY;
}
ErrorResult rv; ErrorResult rv;
MozSetFileNameArray(list, rv); MozSetFileNameArray(list, rv);
return rv.StealNSResult(); return rv.StealNSResult();

View File

@ -49,7 +49,7 @@ interface nsIJSRAIIHelper;
interface nsIContentPermissionRequest; interface nsIContentPermissionRequest;
interface nsIObserver; interface nsIObserver;
[scriptable, uuid(0ce789cc-3fb6-48b8-a58e-32deefc337b4)] [scriptable, uuid(7719798a-62fa-4dff-a6ed-782256649232)]
interface nsIDOMWindowUtils : nsISupports { interface nsIDOMWindowUtils : nsISupports {
/** /**
@ -1428,6 +1428,14 @@ interface nsIDOMWindowUtils : nsISupports {
*/ */
void setAsyncScrollOffset(in nsIDOMNode aNode, in int32_t aX, in int32_t aY); void setAsyncScrollOffset(in nsIDOMNode aNode, in int32_t aX, in int32_t aY);
/**
* Set async zoom value. aRootElement should be the document element of our
* document. The next composite will render with that zoom added to any
* existing zoom if async scrolling is enabled, and then the zoom will be
* removed. Only call this while test-controlled refreshes is enabled.
*/
void setAsyncZoom(in nsIDOMNode aRootElement, in float aValue);
/** /**
* Method for testing StyleAnimationValue::ComputeDistance. * Method for testing StyleAnimationValue::ComputeDistance.
* *

View File

@ -1226,25 +1226,21 @@ bool TabParent::SendRealMouseEvent(WidgetMouseEvent& event)
nsCOMPtr<nsIWidget> widget = GetWidget(); nsCOMPtr<nsIWidget> widget = GetWidget();
if (widget) { if (widget) {
// When we mouseenter the tab, the tab's cursor should become the current // When we mouseenter the tab, the tab's cursor should
// cursor. When we mouseexit, we stop. // become the current cursor. When we mouseexit, we stop.
if (event.message == NS_MOUSE_ENTER_WIDGET || if (NS_MOUSE_ENTER_WIDGET == event.message) {
event.message == NS_MOUSE_OVER) {
mTabSetsCursor = true; mTabSetsCursor = true;
if (mCustomCursor) { if (mCustomCursor) {
widget->SetCursor(mCustomCursor, mCustomCursorHotspotX, mCustomCursorHotspotY); widget->SetCursor(mCustomCursor, mCustomCursorHotspotX, mCustomCursorHotspotY);
} else if (mCursor != nsCursor(-1)) { } else if (mCursor != nsCursor(-1)) {
widget->SetCursor(mCursor); widget->SetCursor(mCursor);
} }
// We don't actually want to forward NS_MOUSE_ENTER_WIDGET messages. } else if (NS_MOUSE_EXIT_WIDGET == event.message) {
return true;
} else if (event.message == NS_MOUSE_EXIT_WIDGET ||
event.message == NS_MOUSE_OUT) {
mTabSetsCursor = false; mTabSetsCursor = false;
} }
} }
if (event.message == NS_MOUSE_MOVE) { if (NS_MOUSE_MOVE == event.message) {
return SendRealMouseMoveEvent(event); return SendRealMouseMoveEvent(event);
} }
return SendRealMouseButtonEvent(event); return SendRealMouseButtonEvent(event);

View File

@ -6,16 +6,15 @@
#include "DecodedStream.h" #include "DecodedStream.h"
#include "MediaStreamGraph.h" #include "MediaStreamGraph.h"
#include "MediaDecoder.h" #include "mozilla/ReentrantMonitor.h"
namespace mozilla { namespace mozilla {
class DecodedStreamGraphListener : public MediaStreamListener { class DecodedStreamGraphListener : public MediaStreamListener {
typedef MediaStreamListener::MediaStreamGraphEvent MediaStreamGraphEvent; typedef MediaStreamListener::MediaStreamGraphEvent MediaStreamGraphEvent;
public: public:
DecodedStreamGraphListener(MediaStream* aStream, DecodedStreamData* aData) explicit DecodedStreamGraphListener(MediaStream* aStream)
: mData(aData) : mMutex("DecodedStreamGraphListener::mMutex")
, mMutex("DecodedStreamGraphListener::mMutex")
, mStream(aStream) , mStream(aStream)
, mLastOutputTime(aStream->StreamTimeToMicroseconds(aStream->GetCurrentTime())) , mLastOutputTime(aStream->StreamTimeToMicroseconds(aStream->GetCurrentTime()))
, mStreamFinishedOnMainThread(false) {} , mStreamFinishedOnMainThread(false) {}
@ -52,7 +51,6 @@ public:
void Forget() void Forget()
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
mData = nullptr;
MutexAutoLock lock(mMutex); MutexAutoLock lock(mMutex);
mStream = nullptr; mStream = nullptr;
} }
@ -64,9 +62,6 @@ public:
} }
private: private:
// Main thread only
DecodedStreamData* mData;
Mutex mMutex; Mutex mMutex;
// Members below are protected by mMutex. // Members below are protected by mMutex.
nsRefPtr<MediaStream> mStream; nsRefPtr<MediaStream> mStream;
@ -74,14 +69,12 @@ private:
bool mStreamFinishedOnMainThread; bool mStreamFinishedOnMainThread;
}; };
DecodedStreamData::DecodedStreamData(MediaDecoder* aDecoder, DecodedStreamData::DecodedStreamData(int64_t aInitialTime,
int64_t aInitialTime,
SourceMediaStream* aStream) SourceMediaStream* aStream)
: mAudioFramesWritten(0) : mAudioFramesWritten(0)
, mInitialTime(aInitialTime) , mInitialTime(aInitialTime)
, mNextVideoTime(-1) , mNextVideoTime(-1)
, mNextAudioTime(-1) , mNextAudioTime(-1)
, mDecoder(aDecoder)
, mStreamInitialized(false) , mStreamInitialized(false)
, mHaveSentFinish(false) , mHaveSentFinish(false)
, mHaveSentFinishAudio(false) , mHaveSentFinishAudio(false)
@ -91,7 +84,7 @@ DecodedStreamData::DecodedStreamData(MediaDecoder* aDecoder,
, mHaveBlockedForStateMachineNotPlaying(false) , mHaveBlockedForStateMachineNotPlaying(false)
, mEOSVideoCompensation(false) , mEOSVideoCompensation(false)
{ {
mListener = new DecodedStreamGraphListener(mStream, this); mListener = new DecodedStreamGraphListener(mStream);
mStream->AddListener(mListener); mStream->AddListener(mListener);
} }
@ -116,8 +109,8 @@ DecodedStreamData::GetClock() const
class OutputStreamListener : public MediaStreamListener { class OutputStreamListener : public MediaStreamListener {
typedef MediaStreamListener::MediaStreamGraphEvent MediaStreamGraphEvent; typedef MediaStreamListener::MediaStreamGraphEvent MediaStreamGraphEvent;
public: public:
OutputStreamListener(MediaDecoder* aDecoder, MediaStream* aStream) OutputStreamListener(DecodedStream* aDecodedStream, MediaStream* aStream)
: mDecoder(aDecoder), mStream(aStream) {} : mDecodedStream(aDecodedStream), mStream(aStream) {}
void NotifyEvent(MediaStreamGraph* aGraph, MediaStreamGraphEvent event) override void NotifyEvent(MediaStreamGraph* aGraph, MediaStreamGraphEvent event) override
{ {
@ -131,22 +124,22 @@ public:
void Forget() void Forget()
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
mDecoder = nullptr; mDecodedStream = nullptr;
} }
private: private:
void DoNotifyFinished() void DoNotifyFinished()
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
if (!mDecoder) { if (!mDecodedStream) {
return; return;
} }
// Remove the finished stream so it won't block the decoded stream. // Remove the finished stream so it won't block the decoded stream.
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); ReentrantMonitorAutoEnter mon(mDecodedStream->GetReentrantMonitor());
auto& streams = mDecoder->OutputStreams(); auto& streams = mDecodedStream->OutputStreams();
// Don't read |mDecoder| in the loop since removing the element will lead // Don't read |mDecodedStream| in the loop since removing the element will lead
// to ~OutputStreamData() which will call Forget() to reset |mDecoder|. // to ~OutputStreamData() which will call Forget() to reset |mDecodedStream|.
for (int32_t i = streams.Length() - 1; i >= 0; --i) { for (int32_t i = streams.Length() - 1; i >= 0; --i) {
auto& os = streams[i]; auto& os = streams[i];
MediaStream* p = os.mStream.get(); MediaStream* p = os.mStream.get();
@ -162,7 +155,7 @@ private:
} }
// Main thread only // Main thread only
MediaDecoder* mDecoder; DecodedStream* mDecodedStream;
nsRefPtr<MediaStream> mStream; nsRefPtr<MediaStream> mStream;
}; };
@ -177,37 +170,54 @@ OutputStreamData::~OutputStreamData()
} }
void void
OutputStreamData::Init(MediaDecoder* aDecoder, ProcessedMediaStream* aStream) OutputStreamData::Init(DecodedStream* aDecodedStream, ProcessedMediaStream* aStream)
{ {
mStream = aStream; mStream = aStream;
mListener = new OutputStreamListener(aDecoder, aStream); mListener = new OutputStreamListener(aDecodedStream, aStream);
aStream->AddListener(mListener); aStream->AddListener(mListener);
} }
DecodedStream::DecodedStream(ReentrantMonitor& aMonitor)
: mMonitor(aMonitor)
{
//
}
DecodedStreamData* DecodedStreamData*
DecodedStream::GetData() DecodedStream::GetData()
{ {
GetReentrantMonitor().AssertCurrentThreadIn();
return mData.get(); return mData.get();
} }
void void
DecodedStream::DestroyData() DecodedStream::DestroyData()
{ {
MOZ_ASSERT(NS_IsMainThread());
GetReentrantMonitor().AssertCurrentThreadIn();
mData = nullptr; mData = nullptr;
} }
void void
DecodedStream::RecreateData(MediaDecoder* aDecoder, int64_t aInitialTime, DecodedStream::RecreateData(int64_t aInitialTime, SourceMediaStream* aStream)
SourceMediaStream* aStream)
{ {
MOZ_ASSERT(NS_IsMainThread());
GetReentrantMonitor().AssertCurrentThreadIn();
MOZ_ASSERT(!mData); MOZ_ASSERT(!mData);
mData.reset(new DecodedStreamData(aDecoder, aInitialTime, aStream)); mData.reset(new DecodedStreamData(aInitialTime, aStream));
} }
nsTArray<OutputStreamData>& nsTArray<OutputStreamData>&
DecodedStream::OutputStreams() DecodedStream::OutputStreams()
{ {
GetReentrantMonitor().AssertCurrentThreadIn();
return mOutputStreams; return mOutputStreams;
} }
ReentrantMonitor&
DecodedStream::GetReentrantMonitor()
{
return mMonitor;
}
} // namespace mozilla } // namespace mozilla

View File

@ -14,13 +14,14 @@
namespace mozilla { namespace mozilla {
class MediaDecoder;
class MediaInputPort; class MediaInputPort;
class SourceMediaStream; class SourceMediaStream;
class ProcessedMediaStream; class ProcessedMediaStream;
class DecodedStream;
class DecodedStreamGraphListener; class DecodedStreamGraphListener;
class OutputStreamData; class OutputStreamData;
class OutputStreamListener; class OutputStreamListener;
class ReentrantMonitor;
namespace layers { namespace layers {
class Image; class Image;
@ -36,8 +37,7 @@ class Image;
*/ */
class DecodedStreamData { class DecodedStreamData {
public: public:
DecodedStreamData(MediaDecoder* aDecoder, int64_t aInitialTime, DecodedStreamData(int64_t aInitialTime, SourceMediaStream* aStream);
SourceMediaStream* aStream);
~DecodedStreamData(); ~DecodedStreamData();
bool IsFinished() const; bool IsFinished() const;
int64_t GetClock() const; int64_t GetClock() const;
@ -55,7 +55,6 @@ public:
// to the output stream. // to the output stream.
int64_t mNextVideoTime; // microseconds int64_t mNextVideoTime; // microseconds
int64_t mNextAudioTime; // microseconds int64_t mNextAudioTime; // microseconds
MediaDecoder* mDecoder;
// The last video image sent to the stream. Useful if we need to replicate // The last video image sent to the stream. Useful if we need to replicate
// the image. // the image.
nsRefPtr<layers::Image> mLastVideoImage; nsRefPtr<layers::Image> mLastVideoImage;
@ -89,7 +88,7 @@ public:
// to work. // to work.
OutputStreamData(); OutputStreamData();
~OutputStreamData(); ~OutputStreamData();
void Init(MediaDecoder* aDecoder, ProcessedMediaStream* aStream); void Init(DecodedStream* aDecodedStream, ProcessedMediaStream* aStream);
nsRefPtr<ProcessedMediaStream> mStream; nsRefPtr<ProcessedMediaStream> mStream;
// mPort connects DecodedStreamData::mStream to our mStream. // mPort connects DecodedStreamData::mStream to our mStream.
nsRefPtr<MediaInputPort> mPort; nsRefPtr<MediaInputPort> mPort;
@ -98,16 +97,18 @@ public:
class DecodedStream { class DecodedStream {
public: public:
explicit DecodedStream(ReentrantMonitor& aMonitor);
DecodedStreamData* GetData(); DecodedStreamData* GetData();
void DestroyData(); void DestroyData();
void RecreateData(MediaDecoder* aDecoder, int64_t aInitialTime, void RecreateData(int64_t aInitialTime, SourceMediaStream* aStream);
SourceMediaStream* aStream);
nsTArray<OutputStreamData>& OutputStreams(); nsTArray<OutputStreamData>& OutputStreams();
ReentrantMonitor& GetReentrantMonitor();
private: private:
UniquePtr<DecodedStreamData> mData; UniquePtr<DecodedStreamData> mData;
// Data about MediaStreams that are being fed by the decoder. // Data about MediaStreams that are being fed by the decoder.
nsTArray<OutputStreamData> mOutputStreams; nsTArray<OutputStreamData> mOutputStreams;
ReentrantMonitor& mMonitor;
}; };
} // namespace mozilla } // namespace mozilla

View File

@ -11,6 +11,20 @@
#include "mozilla/TypeTraits.h" #include "mozilla/TypeTraits.h"
#include "nsTArray.h" #include "nsTArray.h"
// Specialization for nsTArray CopyChooser.
namespace mozilla {
namespace media {
template<class T>
class IntervalSet;
}
}
template<class E>
struct nsTArray_CopyChooser<mozilla::media::IntervalSet<E>>
{
typedef nsTArray_CopyWithConstructors<mozilla::media::IntervalSet<E>> Type;
};
namespace mozilla { namespace mozilla {
namespace media { namespace media {
@ -198,6 +212,15 @@ public:
mFuzz = aFuzz; mFuzz = aFuzz;
} }
// Returns true if the two intervals intersect with this being on the right
// of aOther
bool TouchesOnRight(const SelfType& aOther) const
{
return aOther.mStart <= mStart &&
(mStart - mFuzz <= aOther.mEnd + aOther.mFuzz) &&
(aOther.mStart - aOther.mFuzz <= mEnd + mFuzz);
}
T mStart; T mStart;
T mEnd; T mEnd;
T mFuzz; T mFuzz;
@ -291,10 +314,14 @@ public:
mIntervals.AppendElement(aInterval); mIntervals.AppendElement(aInterval);
return *this; return *this;
} }
ElemType& last = mIntervals.LastElement();
if (aInterval.TouchesOnRight(last)) {
last = last.Span(aInterval);
return *this;
}
// Most of our actual usage is adding an interval that will be outside the // Most of our actual usage is adding an interval that will be outside the
// range. We can speed up normalization here. // range. We can speed up normalization here.
if (aInterval.RightOf(mIntervals.LastElement()) && if (aInterval.RightOf(last)) {
!aInterval.Touches(mIntervals.LastElement())) {
mIntervals.AppendElement(aInterval); mIntervals.AppendElement(aInterval);
return *this; return *this;
} }
@ -510,12 +537,13 @@ public:
} }
// Shift all values by aOffset. // Shift all values by aOffset.
void Shift(const T& aOffset) SelfType& Shift(const T& aOffset)
{ {
for (auto& interval : mIntervals) { for (auto& interval : mIntervals) {
interval.mStart = interval.mStart + aOffset; interval.mStart = interval.mStart + aOffset;
interval.mEnd = interval.mEnd + aOffset; interval.mEnd = interval.mEnd + aOffset;
} }
return *this;
} }
void SetFuzz(const T& aFuzz) { void SetFuzz(const T& aFuzz) {

View File

@ -85,6 +85,12 @@ public:
// data is available. // data is available.
virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset) { } virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset) { }
// Notifies the demuxer that the underlying resource has had data removed.
// The demuxer can use this mechanism to inform all track demuxers to update
// its TimeIntervals.
// This will be called should the demuxer be used with MediaSource.
virtual void NotifyDataRemoved() { }
protected: protected:
virtual ~MediaDataDemuxer() virtual ~MediaDataDemuxer()
{ {

View File

@ -384,7 +384,7 @@ void MediaDecoder::RecreateDecodedStream(int64_t aStartTimeUSecs,
} }
DestroyDecodedStream(); DestroyDecodedStream();
mDecodedStream.RecreateData(this, aStartTimeUSecs, aGraph->CreateSourceStream(nullptr)); mDecodedStream.RecreateData(aStartTimeUSecs, aGraph->CreateSourceStream(nullptr));
// Note that the delay between removing ports in DestroyDecodedStream // Note that the delay between removing ports in DestroyDecodedStream
// and adding new ones won't cause a glitch since all graph operations // and adding new ones won't cause a glitch since all graph operations
@ -419,7 +419,7 @@ void MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream,
RecreateDecodedStream(mLogicalPosition, aStream->Graph()); RecreateDecodedStream(mLogicalPosition, aStream->Graph());
} }
OutputStreamData* os = OutputStreams().AppendElement(); OutputStreamData* os = OutputStreams().AppendElement();
os->Init(this, aStream); os->Init(&mDecodedStream, aStream);
ConnectDecodedStreamToOutputStream(os); ConnectDecodedStreamToOutputStream(os);
if (aFinishWhenEnded) { if (aFinishWhenEnded) {
// Ensure that aStream finishes the moment mDecodedStream does. // Ensure that aStream finishes the moment mDecodedStream does.
@ -483,6 +483,7 @@ MediaDecoder::MediaDecoder() :
mMediaSeekable(true), mMediaSeekable(true),
mSameOriginMedia(false), mSameOriginMedia(false),
mReentrantMonitor("media.decoder"), mReentrantMonitor("media.decoder"),
mDecodedStream(mReentrantMonitor),
mPlayState(AbstractThread::MainThread(), PLAY_STATE_LOADING, mPlayState(AbstractThread::MainThread(), PLAY_STATE_LOADING,
"MediaDecoder::mPlayState (Canonical)"), "MediaDecoder::mPlayState (Canonical)"),
mNextState(AbstractThread::MainThread(), PLAY_STATE_PAUSED, mNextState(AbstractThread::MainThread(), PLAY_STATE_PAUSED,

View File

@ -247,6 +247,8 @@ public:
// Only used by WebMReader and MediaOmxReader for now, so stub here rather // Only used by WebMReader and MediaOmxReader for now, so stub here rather
// than in every reader than inherits from MediaDecoderReader. // than in every reader than inherits from MediaDecoderReader.
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) {} virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) {}
// Notify the reader that data from the resource was evicted (MediaSource only)
virtual void NotifyDataRemoved() {}
virtual int64_t GetEvictionOffset(double aTime) { return -1; } virtual int64_t GetEvictionOffset(double aTime) { return -1; }
virtual MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; } virtual MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }

View File

@ -69,6 +69,7 @@ MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
, mInitDone(false) , mInitDone(false)
, mSeekable(false) , mSeekable(false)
, mIsEncrypted(false) , mIsEncrypted(false)
, mCachedTimeRangesStale(true)
#if defined(READER_DORMANT_HEURISTIC) #if defined(READER_DORMANT_HEURISTIC)
, mDormantEnabled(Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false)) , mDormantEnabled(Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false))
#endif #endif
@ -107,6 +108,7 @@ MediaFormatReader::Shutdown()
mAudio.mDecoder = nullptr; mAudio.mDecoder = nullptr;
} }
if (mAudio.mTrackDemuxer) { if (mAudio.mTrackDemuxer) {
mAudio.ResetDemuxer();
mAudio.mTrackDemuxer->BreakCycles(); mAudio.mTrackDemuxer->BreakCycles();
mAudio.mTrackDemuxer = nullptr; mAudio.mTrackDemuxer = nullptr;
} }
@ -123,6 +125,7 @@ MediaFormatReader::Shutdown()
mVideo.mDecoder = nullptr; mVideo.mDecoder = nullptr;
} }
if (mVideo.mTrackDemuxer) { if (mVideo.mTrackDemuxer) {
mVideo.ResetDemuxer();
mVideo.mTrackDemuxer->BreakCycles(); mVideo.mTrackDemuxer->BreakCycles();
mVideo.mTrackDemuxer = nullptr; mVideo.mTrackDemuxer = nullptr;
} }
@ -509,6 +512,7 @@ MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe,
MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise(), "No duplicate sample requests"); MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise(), "No duplicate sample requests");
MOZ_DIAGNOSTIC_ASSERT(!mVideoSeekRequest.Exists()); MOZ_DIAGNOSTIC_ASSERT(!mVideoSeekRequest.Exists());
MOZ_DIAGNOSTIC_ASSERT(!mAudioSeekRequest.Exists()); MOZ_DIAGNOSTIC_ASSERT(!mAudioSeekRequest.Exists());
MOZ_DIAGNOSTIC_ASSERT(!mSkipRequest.Exists(), "called mid-skipping");
MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek"); MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
LOGV("RequestVideoData(%d, %lld)", aSkipToNextKeyframe, aTimeThreshold); LOGV("RequestVideoData(%d, %lld)", aSkipToNextKeyframe, aTimeThreshold);
@ -656,6 +660,8 @@ void
MediaFormatReader::NotifyNewOutput(TrackType aTrack, MediaData* aSample) MediaFormatReader::NotifyNewOutput(TrackType aTrack, MediaData* aSample)
{ {
MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(OnTaskQueue());
LOGV("Received new sample time:%lld duration:%lld",
aSample->mTime, aSample->mDuration);
auto& decoder = GetDecoderData(aTrack); auto& decoder = GetDecoderData(aTrack);
if (!decoder.mOutputRequested) { if (!decoder.mOutputRequested) {
LOG("MediaFormatReader produced output while flushing, discarding."); LOG("MediaFormatReader produced output while flushing, discarding.");
@ -670,6 +676,7 @@ void
MediaFormatReader::NotifyInputExhausted(TrackType aTrack) MediaFormatReader::NotifyInputExhausted(TrackType aTrack)
{ {
MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(OnTaskQueue());
LOGV("Decoder has requested more %s data", TrackTypeToStr(aTrack));
auto& decoder = GetDecoderData(aTrack); auto& decoder = GetDecoderData(aTrack);
decoder.mInputExhausted = true; decoder.mInputExhausted = true;
ScheduleUpdate(aTrack); ScheduleUpdate(aTrack);
@ -692,6 +699,7 @@ void
MediaFormatReader::NotifyError(TrackType aTrack) MediaFormatReader::NotifyError(TrackType aTrack)
{ {
MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(OnTaskQueue());
LOGV("%s Decoding error", TrackTypeToStr(aTrack));
auto& decoder = GetDecoderData(aTrack); auto& decoder = GetDecoderData(aTrack);
decoder.mError = true; decoder.mError = true;
ScheduleUpdate(aTrack); ScheduleUpdate(aTrack);
@ -718,6 +726,7 @@ MediaFormatReader::NeedInput(DecoderData& aDecoder)
return return
!aDecoder.mError && !aDecoder.mError &&
aDecoder.HasPromise() && aDecoder.HasPromise() &&
!aDecoder.mDemuxRequest.Exists() &&
aDecoder.mOutput.IsEmpty() && aDecoder.mOutput.IsEmpty() &&
(aDecoder.mInputExhausted || !aDecoder.mQueuedSamples.IsEmpty() || (aDecoder.mInputExhausted || !aDecoder.mQueuedSamples.IsEmpty() ||
aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput < aDecoder.mDecodeAhead); aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput < aDecoder.mDecodeAhead);
@ -766,13 +775,16 @@ MediaFormatReader::UpdateReceivedNewData(TrackType aTrack)
decoder.mDemuxEOSServiced = false; decoder.mDemuxEOSServiced = false;
} }
if (!decoder.mError && decoder.HasWaitingPromise()) { if (decoder.mError) {
return false;
}
if (decoder.HasWaitingPromise()) {
MOZ_ASSERT(!decoder.HasPromise()); MOZ_ASSERT(!decoder.HasPromise());
LOG("We have new data. Resolving WaitingPromise"); LOG("We have new data. Resolving WaitingPromise");
decoder.mWaitingPromise.Resolve(decoder.mType, __func__); decoder.mWaitingPromise.Resolve(decoder.mType, __func__);
return true; return true;
} }
if (!decoder.mError && !mSeekPromise.IsEmpty()) { if (!mSeekPromise.IsEmpty()) {
MOZ_ASSERT(!decoder.HasPromise()); MOZ_ASSERT(!decoder.HasPromise());
if (mVideoSeekRequest.Exists() || mAudioSeekRequest.Exists()) { if (mVideoSeekRequest.Exists() || mAudioSeekRequest.Exists()) {
// Already waiting for a seek to complete. Nothing more to do. // Already waiting for a seek to complete. Nothing more to do.
@ -790,20 +802,19 @@ MediaFormatReader::RequestDemuxSamples(TrackType aTrack)
{ {
MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(OnTaskQueue());
auto& decoder = GetDecoderData(aTrack); auto& decoder = GetDecoderData(aTrack);
MOZ_ASSERT(!decoder.mDemuxRequest.Exists());
if (!decoder.mQueuedSamples.IsEmpty()) { if (!decoder.mQueuedSamples.IsEmpty()) {
// No need to demux new samples. // No need to demux new samples.
return; return;
} }
if (decoder.mDemuxRequest.Exists()) {
// We are already in the process of demuxing.
return;
}
if (decoder.mDemuxEOS) { if (decoder.mDemuxEOS) {
// Nothing left to demux. // Nothing left to demux.
return; return;
} }
LOGV("Requesting extra demux %s", TrackTypeToStr(aTrack));
if (aTrack == TrackInfo::kVideoTrack) { if (aTrack == TrackInfo::kVideoTrack) {
DoDemuxVideo(); DoDemuxVideo();
} else { } else {
@ -821,7 +832,7 @@ MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack,
if (decoder.mQueuedSamples.IsEmpty()) { if (decoder.mQueuedSamples.IsEmpty()) {
return; return;
} }
LOGV("Giving %s input to decoder", TrackTypeToStr(aTrack));
decoder.mOutputRequested = true; decoder.mOutputRequested = true;
// Decode all our demuxed frames. // Decode all our demuxed frames.
@ -845,9 +856,7 @@ MediaFormatReader::Update(TrackType aTrack)
return; return;
} }
// Record number of frames decoded and parsed. Automatically update the LOGV("Processing update for %s", TrackTypeToStr(aTrack));
// stats counters using the AutoNotifyDecoded stack-based class.
AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
bool needInput = false; bool needInput = false;
bool needOutput = false; bool needOutput = false;
@ -855,6 +864,7 @@ MediaFormatReader::Update(TrackType aTrack)
decoder.mUpdateScheduled = false; decoder.mUpdateScheduled = false;
if (UpdateReceivedNewData(aTrack)) { if (UpdateReceivedNewData(aTrack)) {
LOGV("Nothing more to do");
return; return;
} }
@ -871,13 +881,14 @@ MediaFormatReader::Update(TrackType aTrack)
} }
} else if (decoder.mWaitingForData) { } else if (decoder.mWaitingForData) {
// Nothing more we can do at present. // Nothing more we can do at present.
LOGV("Still waiting for data.");
return; return;
} }
if (NeedInput(decoder)) { // Record number of frames decoded and parsed. Automatically update the
needInput = true; // stats counters using the AutoNotifyDecoded stack-based class.
decoder.mInputExhausted = false; AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
}
if (aTrack == TrackInfo::kVideoTrack) { if (aTrack == TrackInfo::kVideoTrack) {
uint64_t delta = uint64_t delta =
decoder.mNumSamplesOutput - mLastReportedNumDecodedFrames; decoder.mNumSamplesOutput - mLastReportedNumDecodedFrames;
@ -898,18 +909,27 @@ MediaFormatReader::Update(TrackType aTrack)
} }
} }
LOGV("Update(%s) ni=%d no=%d", TrackTypeToStr(aTrack), needInput, needOutput);
if (decoder.mDemuxEOS && !decoder.mDemuxEOSServiced) { if (decoder.mDemuxEOS && !decoder.mDemuxEOSServiced) {
decoder.mOutputRequested = true; decoder.mOutputRequested = true;
decoder.mDecoder->Drain(); decoder.mDecoder->Drain();
decoder.mDemuxEOSServiced = true; decoder.mDemuxEOSServiced = true;
} LOGV("Requesting decoder to drain");
if (!needInput) {
return; return;
} }
if (!NeedInput(decoder)) {
LOGV("No need for additional input");
return;
}
needInput = true;
decoder.mInputExhausted = false;
LOGV("Update(%s) ni=%d no=%d ie=%d, in:%d out:%d qs=%d",
TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted,
decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
size_t(decoder.mSizeOfQueue));
// Demux samples if we don't have some. // Demux samples if we don't have some.
RequestDemuxSamples(aTrack); RequestDemuxSamples(aTrack);
// Decode all pending demuxed samples. // Decode all pending demuxed samples.
@ -922,6 +942,7 @@ MediaFormatReader::ReturnOutput(MediaData* aData, TrackType aTrack)
auto& decoder = GetDecoderData(aTrack); auto& decoder = GetDecoderData(aTrack);
MOZ_ASSERT(decoder.HasPromise()); MOZ_ASSERT(decoder.HasPromise());
if (decoder.mDiscontinuity) { if (decoder.mDiscontinuity) {
LOGV("Setting discontinuity flag");
decoder.mDiscontinuity = false; decoder.mDiscontinuity = false;
aData->mDiscontinuity = true; aData->mDiscontinuity = true;
} }
@ -941,6 +962,7 @@ MediaFormatReader::ReturnOutput(MediaData* aData, TrackType aTrack)
} else if (aTrack == TrackInfo::kVideoTrack) { } else if (aTrack == TrackInfo::kVideoTrack) {
mVideo.mPromise.Resolve(static_cast<VideoData*>(aData), __func__); mVideo.mPromise.Resolve(static_cast<VideoData*>(aData), __func__);
} }
LOG("Resolved data promise for %s", TrackTypeToStr(aTrack));
} }
size_t size_t
@ -979,6 +1001,7 @@ MediaFormatReader::ResetDecode()
{ {
MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(OnTaskQueue());
LOGV("");
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mAudioSeekRequest.DisconnectIfExists(); mAudioSeekRequest.DisconnectIfExists();
@ -994,9 +1017,11 @@ MediaFormatReader::ResetDecode()
mPendingSeekTime.reset(); mPendingSeekTime.reset();
if (HasVideo()) { if (HasVideo()) {
mVideo.ResetDemuxer();
Flush(TrackInfo::kVideoTrack); Flush(TrackInfo::kVideoTrack);
} }
if (HasAudio()) { if (HasAudio()) {
mAudio.ResetDemuxer();
Flush(TrackInfo::kAudioTrack); Flush(TrackInfo::kAudioTrack);
} }
return MediaDecoderReader::ResetDecode(); return MediaDecoderReader::ResetDecode();
@ -1015,7 +1040,7 @@ MediaFormatReader::Output(TrackType aTrack, MediaData* aSample)
} }
RefPtr<nsIRunnable> task = RefPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArgs<TrackType, StorensRefPtrPassByPtr<MediaData>>( NS_NewRunnableMethodWithArgs<TrackType, MediaData*>(
this, &MediaFormatReader::NotifyNewOutput, aTrack, aSample); this, &MediaFormatReader::NotifyNewOutput, aTrack, aSample);
GetTaskQueue()->Dispatch(task.forget()); GetTaskQueue()->Dispatch(task.forget());
} }
@ -1058,9 +1083,6 @@ MediaFormatReader::Flush(TrackType aTrack)
return; return;
} }
// Clear demuxer related data.
decoder.mDemuxRequest.DisconnectIfExists();
decoder.mTrackDemuxer->Reset();
decoder.mDecoder->Flush(); decoder.mDecoder->Flush();
// Purge the current decoder's state. // Purge the current decoder's state.
// ResetState clears mOutputRequested flag so that we ignore all output until // ResetState clears mOutputRequested flag so that we ignore all output until
@ -1167,6 +1189,7 @@ void
MediaFormatReader::OnSeekFailed(TrackType aTrack, DemuxerFailureReason aResult) MediaFormatReader::OnSeekFailed(TrackType aTrack, DemuxerFailureReason aResult)
{ {
MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(OnTaskQueue());
LOGV("%s failure = %d", TrackTypeToStr(aTrack), aResult);
if (aTrack == TrackType::kVideoTrack) { if (aTrack == TrackType::kVideoTrack) {
mVideoSeekRequest.Complete(); mVideoSeekRequest.Complete();
} else { } else {
@ -1186,6 +1209,7 @@ void
MediaFormatReader::DoVideoSeek() MediaFormatReader::DoVideoSeek()
{ {
MOZ_ASSERT(mPendingSeekTime.isSome()); MOZ_ASSERT(mPendingSeekTime.isSome());
LOGV("Seeking video to %lld", mPendingSeekTime.ref().ToMicroseconds());
media::TimeUnit seekTime = mPendingSeekTime.ref(); media::TimeUnit seekTime = mPendingSeekTime.ref();
mVideoSeekRequest.Begin(mVideo.mTrackDemuxer->Seek(seekTime) mVideoSeekRequest.Begin(mVideo.mTrackDemuxer->Seek(seekTime)
->RefableThen(GetTaskQueue(), __func__, this, ->RefableThen(GetTaskQueue(), __func__, this,
@ -1197,6 +1221,7 @@ void
MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime) MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime)
{ {
MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(OnTaskQueue());
LOGV("Video seeked to %lld", aTime.ToMicroseconds());
mVideoSeekRequest.Complete(); mVideoSeekRequest.Complete();
if (HasAudio()) { if (HasAudio()) {
@ -1213,6 +1238,7 @@ void
MediaFormatReader::DoAudioSeek() MediaFormatReader::DoAudioSeek()
{ {
MOZ_ASSERT(mPendingSeekTime.isSome()); MOZ_ASSERT(mPendingSeekTime.isSome());
LOGV("Seeking audio to %lld", mPendingSeekTime.ref().ToMicroseconds());
media::TimeUnit seekTime = mPendingSeekTime.ref(); media::TimeUnit seekTime = mPendingSeekTime.ref();
mAudioSeekRequest.Begin(mAudio.mTrackDemuxer->Seek(seekTime) mAudioSeekRequest.Begin(mAudio.mTrackDemuxer->Seek(seekTime)
->RefableThen(GetTaskQueue(), __func__, this, ->RefableThen(GetTaskQueue(), __func__, this,
@ -1224,6 +1250,7 @@ void
MediaFormatReader::OnAudioSeekCompleted(media::TimeUnit aTime) MediaFormatReader::OnAudioSeekCompleted(media::TimeUnit aTime)
{ {
MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(OnTaskQueue());
LOGV("Audio seeked to %lld", aTime.ToMicroseconds());
mAudioSeekRequest.Complete(); mAudioSeekRequest.Complete();
mPendingSeekTime.reset(); mPendingSeekTime.reset();
mSeekPromise.Resolve(aTime.ToMicroseconds(), __func__); mSeekPromise.Resolve(aTime.ToMicroseconds(), __func__);
@ -1250,15 +1277,50 @@ MediaFormatReader::GetBuffered()
{ {
media::TimeIntervals videoti; media::TimeIntervals videoti;
media::TimeIntervals audioti; media::TimeIntervals audioti;
media::TimeIntervals intervals;
if (!mInitDone) {
return intervals;
}
int64_t startTime;
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
MOZ_ASSERT(mStartTime != -1, "Need to finish metadata decode first");
startTime = mStartTime;
}
if (NS_IsMainThread()) { if (NS_IsMainThread()) {
if (mVideoTrackDemuxer) { if (mCachedTimeRangesStale) {
videoti = mVideoTrackDemuxer->GetBuffered(); MOZ_ASSERT(mMainThreadDemuxer);
} if (!mDataRange.IsEmpty()) {
if (mAudioTrackDemuxer) { mMainThreadDemuxer->NotifyDataArrived(mDataRange.Length(), mDataRange.mStart);
audioti = mAudioTrackDemuxer->GetBuffered(); }
if (mVideoTrackDemuxer) {
videoti = mVideoTrackDemuxer->GetBuffered();
}
if (mAudioTrackDemuxer) {
audioti = mAudioTrackDemuxer->GetBuffered();
}
if (HasAudio() && HasVideo()) {
mCachedTimeRanges = media::Intersection(Move(videoti), Move(audioti));
} else if (HasAudio()) {
mCachedTimeRanges = Move(audioti);
} else if (HasVideo()) {
mCachedTimeRanges = Move(videoti);
}
mDataRange = ByteInterval();
mCachedTimeRangesStale = false;
} }
intervals = mCachedTimeRanges;
} else { } else {
if (OnTaskQueue()) {
// Ensure we have up to date buffered time range.
if (HasVideo()) {
UpdateReceivedNewData(TrackType::kVideoTrack);
}
if (HasAudio()) {
UpdateReceivedNewData(TrackType::kAudioTrack);
}
}
if (HasVideo()) { if (HasVideo()) {
MonitorAutoLock lock(mVideo.mMonitor); MonitorAutoLock lock(mVideo.mMonitor);
videoti = mVideo.mTimeRanges; videoti = mVideo.mTimeRanges;
@ -1267,17 +1329,16 @@ MediaFormatReader::GetBuffered()
MonitorAutoLock lock(mAudio.mMonitor); MonitorAutoLock lock(mAudio.mMonitor);
audioti = mAudio.mTimeRanges; audioti = mAudio.mTimeRanges;
} }
} if (HasAudio() && HasVideo()) {
if (HasAudio() && HasVideo()) { intervals = media::Intersection(Move(videoti), Move(audioti));
videoti.Intersection(audioti); } else if (HasAudio()) {
return videoti; intervals = Move(audioti);
} else if (HasAudio()) { } else if (HasVideo()) {
return audioti; intervals = Move(videoti);
} else if (HasVideo()) { }
return videoti;
} }
return media::TimeIntervals(); return intervals.Shift(media::TimeUnit::FromMicroseconds(-startTime));
} }
bool MediaFormatReader::IsDormantNeeded() bool MediaFormatReader::IsDormantNeeded()
@ -1330,11 +1391,16 @@ MediaFormatReader::NotifyDemuxer(uint32_t aLength, int64_t aOffset)
{ {
MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(OnTaskQueue());
LOGV("aLength=%u, aOffset=%lld", aLength, aOffset);
if (mShutdown) { if (mShutdown) {
return; return;
} }
mDemuxer->NotifyDataArrived(aLength, aOffset); if (aLength || aOffset) {
mDemuxer->NotifyDataArrived(aLength, aOffset);
} else {
mDemuxer->NotifyDataRemoved();
}
if (HasVideo()) { if (HasVideo()) {
mVideo.mReceivedNewData = true; mVideo.mReceivedNewData = true;
ScheduleUpdate(TrackType::kVideoTrack); ScheduleUpdate(TrackType::kVideoTrack);
@ -1350,13 +1416,18 @@ MediaFormatReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int6
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aBuffer || aLength);
if (mDataRange.IsEmpty()) {
mDataRange = ByteInterval(aOffset, aOffset + aLength);
} else {
mDataRange = mDataRange.Span(ByteInterval(aOffset, aOffset + aLength));
}
mCachedTimeRangesStale = true;
if (!mInitDone) { if (!mInitDone) {
return; return;
} }
MOZ_ASSERT(mMainThreadDemuxer);
mMainThreadDemuxer->NotifyDataArrived(aLength, aOffset);
// Queue a task to notify our main demuxer. // Queue a task to notify our main demuxer.
RefPtr<nsIRunnable> task = RefPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArgs<int32_t, uint64_t>( NS_NewRunnableMethodWithArgs<int32_t, uint64_t>(
@ -1365,4 +1436,27 @@ MediaFormatReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int6
GetTaskQueue()->Dispatch(task.forget()); GetTaskQueue()->Dispatch(task.forget());
} }
void
MediaFormatReader::NotifyDataRemoved()
{
MOZ_ASSERT(NS_IsMainThread());
mDataRange = ByteInterval();
mCachedTimeRangesStale = true;
if (!mInitDone) {
return;
}
MOZ_ASSERT(mMainThreadDemuxer);
mMainThreadDemuxer->NotifyDataRemoved();
// Queue a task to notify our main demuxer.
RefPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArgs<int32_t, uint64_t>(
this, &MediaFormatReader::NotifyDemuxer,
0, 0);
GetTaskQueue()->Dispatch(task.forget());
}
} // namespace mozilla } // namespace mozilla

View File

@ -26,6 +26,7 @@ namespace mozilla {
class MediaFormatReader final : public MediaDecoderReader class MediaFormatReader final : public MediaDecoderReader
{ {
typedef TrackInfo::TrackType TrackType; typedef TrackInfo::TrackType TrackType;
typedef media::Interval<int64_t> ByteInterval;
public: public:
explicit MediaFormatReader(AbstractMediaDecoder* aDecoder, explicit MediaFormatReader(AbstractMediaDecoder* aDecoder,
@ -76,6 +77,7 @@ public:
virtual void NotifyDataArrived(const char* aBuffer, virtual void NotifyDataArrived(const char* aBuffer,
uint32_t aLength, uint32_t aLength,
int64_t aOffset) override; int64_t aOffset) override;
virtual void NotifyDataRemoved() override;
virtual media::TimeIntervals GetBuffered() override; virtual media::TimeIntervals GetBuffered() override;
@ -103,6 +105,9 @@ public:
private: private:
bool InitDemuxer(); bool InitDemuxer();
// Notify the demuxer that new data has been received.
// The next queued task calling GetBuffered() is guaranteed to have up to date
// buffered ranges.
void NotifyDemuxer(uint32_t aLength, int64_t aOffset); void NotifyDemuxer(uint32_t aLength, int64_t aOffset);
void ReturnOutput(MediaData* aData, TrackType aTrack); void ReturnOutput(MediaData* aData, TrackType aTrack);
@ -251,6 +256,13 @@ private:
virtual void RejectPromise(MediaDecoderReader::NotDecodedReason aReason, virtual void RejectPromise(MediaDecoderReader::NotDecodedReason aReason,
const char* aMethodName) = 0; const char* aMethodName) = 0;
void ResetDemuxer()
{
// Clear demuxer related data.
mDemuxRequest.DisconnectIfExists();
mTrackDemuxer->Reset();
}
void ResetState() void ResetState()
{ {
MOZ_ASSERT(mOwner->OnTaskQueue()); MOZ_ASSERT(mOwner->OnTaskQueue());
@ -304,7 +316,6 @@ private:
DecoderDataWithPromise<VideoDataPromise> mVideo; DecoderDataWithPromise<VideoDataPromise> mVideo;
// Returns true when the decoder for this track needs input. // Returns true when the decoder for this track needs input.
// aDecoder.mMonitor must be locked.
bool NeedInput(DecoderData& aDecoder); bool NeedInput(DecoderData& aDecoder);
DecoderData& GetDecoderData(TrackType aTrack); DecoderData& GetDecoderData(TrackType aTrack);
@ -389,9 +400,14 @@ private:
nsRefPtr<SharedDecoderManager> mSharedDecoderManager; nsRefPtr<SharedDecoderManager> mSharedDecoderManager;
// Main thread objects // Main thread objects
// Those are only used to calculate our buffered range on the main thread.
// The cached buffered range is calculated one when required.
nsRefPtr<MediaDataDemuxer> mMainThreadDemuxer; nsRefPtr<MediaDataDemuxer> mMainThreadDemuxer;
nsRefPtr<MediaTrackDemuxer> mAudioTrackDemuxer; nsRefPtr<MediaTrackDemuxer> mAudioTrackDemuxer;
nsRefPtr<MediaTrackDemuxer> mVideoTrackDemuxer; nsRefPtr<MediaTrackDemuxer> mVideoTrackDemuxer;
ByteInterval mDataRange;
media::TimeIntervals mCachedTimeRanges;
bool mCachedTimeRangesStale;
#if defined(READER_DORMANT_HEURISTIC) #if defined(READER_DORMANT_HEURISTIC)
const bool mDormantEnabled; const bool mDormantEnabled;

View File

@ -12,6 +12,18 @@
#include "mozilla/FloatingPoint.h" #include "mozilla/FloatingPoint.h"
#include "mozilla/dom/TimeRanges.h" #include "mozilla/dom/TimeRanges.h"
namespace mozilla {
namespace media {
class TimeIntervals;
}
}
// CopyChooser specalization for nsTArray
template<>
struct nsTArray_CopyChooser<mozilla::media::TimeIntervals>
{
typedef nsTArray_CopyWithConstructors<mozilla::media::TimeIntervals> Type;
};
namespace mozilla { namespace mozilla {
namespace media { namespace media {

View File

@ -114,6 +114,14 @@ MP4Demuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset)
} }
} }
void
MP4Demuxer::NotifyDataRemoved()
{
for (uint32_t i = 0; i < mDemuxers.Length(); i++) {
mDemuxers[i]->NotifyDataArrived();
}
}
UniquePtr<EncryptionInfo> UniquePtr<EncryptionInfo>
MP4Demuxer::GetCrypto() MP4Demuxer::GetCrypto()
{ {
@ -143,6 +151,7 @@ MP4TrackDemuxer::MP4TrackDemuxer(MP4Demuxer* aParent,
uint32_t aTrackNumber) uint32_t aTrackNumber)
: mParent(aParent) : mParent(aParent)
, mStream(new mp4_demuxer::ResourceStream(mParent->mResource)) , mStream(new mp4_demuxer::ResourceStream(mParent->mResource))
, mNeedReIndex(true)
, mMonitor("MP4TrackDemuxer") , mMonitor("MP4TrackDemuxer")
{ {
mInfo = mParent->mMetadata->GetTrackInfo(aType, aTrackNumber); mInfo = mParent->mMetadata->GetTrackInfo(aType, aTrackNumber);
@ -168,6 +177,23 @@ MP4TrackDemuxer::GetInfo() const
return mInfo->Clone(); return mInfo->Clone();
} }
void
MP4TrackDemuxer::EnsureUpToDateIndex()
{
if (!mNeedReIndex) {
return;
}
AutoPinned<MediaResource> resource(mParent->mResource);
nsTArray<MediaByteRange> byteRanges;
nsresult rv = resource->GetCachedRanges(byteRanges);
if (NS_FAILED(rv)) {
return;
}
MonitorAutoLock mon(mMonitor);
mIndex->UpdateMoofIndex(byteRanges);
mNeedReIndex = false;
}
nsRefPtr<MP4TrackDemuxer::SeekPromise> nsRefPtr<MP4TrackDemuxer::SeekPromise>
MP4TrackDemuxer::Seek(media::TimeUnit aTime) MP4TrackDemuxer::Seek(media::TimeUnit aTime)
{ {
@ -182,6 +208,7 @@ MP4TrackDemuxer::Seek(media::TimeUnit aTime)
if (mQueuedSample) { if (mQueuedSample) {
seekTime = mQueuedSample->mTime; seekTime = mQueuedSample->mTime;
} }
SetNextKeyFrameTime();
return SeekPromise::CreateAndResolve(media::TimeUnit::FromMicroseconds(seekTime), __func__); return SeekPromise::CreateAndResolve(media::TimeUnit::FromMicroseconds(seekTime), __func__);
} }
@ -214,6 +241,17 @@ MP4TrackDemuxer::GetSamples(int32_t aNumSamples)
} }
} }
void
MP4TrackDemuxer::SetNextKeyFrameTime()
{
mNextKeyframeTime.reset();
mp4_demuxer::Microseconds frameTime = mIterator->GetNextKeyframeTime();
if (frameTime != -1) {
mNextKeyframeTime.emplace(
media::TimeUnit::FromMicroseconds(frameTime));
}
}
void void
MP4TrackDemuxer::Reset() MP4TrackDemuxer::Reset()
{ {
@ -221,6 +259,7 @@ MP4TrackDemuxer::Reset()
// TODO, Seek to first frame available, which isn't always 0. // TODO, Seek to first frame available, which isn't always 0.
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
mIterator->Seek(0); mIterator->Seek(0);
SetNextKeyFrameTime();
} }
void void
@ -240,12 +279,7 @@ MP4TrackDemuxer::UpdateSamples(nsTArray<nsRefPtr<MediaRawData>>& aSamples)
} }
if (mNextKeyframeTime.isNothing() || if (mNextKeyframeTime.isNothing() ||
aSamples.LastElement()->mTime >= mNextKeyframeTime.value().ToMicroseconds()) { aSamples.LastElement()->mTime >= mNextKeyframeTime.value().ToMicroseconds()) {
mNextKeyframeTime.reset(); SetNextKeyFrameTime();
mp4_demuxer::Microseconds frameTime = mIterator->GetNextKeyframeTime();
if (frameTime != -1) {
mNextKeyframeTime.emplace(
media::TimeUnit::FromMicroseconds(frameTime));
}
} }
} }
@ -278,6 +312,7 @@ MP4TrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold)
mQueuedSample = sample; mQueuedSample = sample;
} }
} }
SetNextKeyFrameTime();
if (found) { if (found) {
return SkipAccessPointPromise::CreateAndResolve(parsed, __func__); return SkipAccessPointPromise::CreateAndResolve(parsed, __func__);
} else { } else {
@ -290,12 +325,14 @@ int64_t
MP4TrackDemuxer::GetEvictionOffset(media::TimeUnit aTime) MP4TrackDemuxer::GetEvictionOffset(media::TimeUnit aTime)
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
return int64_t(mIndex->GetEvictionOffset(aTime.ToMicroseconds())); uint64_t offset = mIndex->GetEvictionOffset(aTime.ToMicroseconds());
return int64_t(offset == std::numeric_limits<uint64_t>::max() ? 0 : offset);
} }
media::TimeIntervals media::TimeIntervals
MP4TrackDemuxer::GetBuffered() MP4TrackDemuxer::GetBuffered()
{ {
EnsureUpToDateIndex();
AutoPinned<MediaResource> resource(mParent->mResource); AutoPinned<MediaResource> resource(mParent->mResource);
nsTArray<MediaByteRange> byteRanges; nsTArray<MediaByteRange> byteRanges;
nsresult rv = resource->GetCachedRanges(byteRanges); nsresult rv = resource->GetCachedRanges(byteRanges);
@ -306,16 +343,9 @@ MP4TrackDemuxer::GetBuffered()
nsTArray<mp4_demuxer::Interval<int64_t>> timeRanges; nsTArray<mp4_demuxer::Interval<int64_t>> timeRanges;
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
int64_t endComposition =
mIndex->GetEndCompositionIfBuffered(byteRanges);
mIndex->ConvertByteRangesToTimeRanges(byteRanges, &timeRanges); mIndex->ConvertByteRangesToTimeRanges(byteRanges, &timeRanges);
if (endComposition) {
mp4_demuxer::Interval<int64_t>::SemiNormalAppend(
timeRanges, mp4_demuxer::Interval<int64_t>(endComposition, endComposition));
}
// convert timeRanges. // convert timeRanges.
media::TimeIntervals ranges; media::TimeIntervals ranges = media::TimeIntervals();
for (size_t i = 0; i < timeRanges.Length(); i++) { for (size_t i = 0; i < timeRanges.Length(); i++) {
ranges += ranges +=
media::TimeInterval(media::TimeUnit::FromMicroseconds(timeRanges[i].start), media::TimeInterval(media::TimeUnit::FromMicroseconds(timeRanges[i].start),
@ -327,14 +357,7 @@ MP4TrackDemuxer::GetBuffered()
void void
MP4TrackDemuxer::NotifyDataArrived() MP4TrackDemuxer::NotifyDataArrived()
{ {
AutoPinned<MediaResource> resource(mParent->mResource); mNeedReIndex = true;
nsTArray<MediaByteRange> byteRanges;
nsresult rv = resource->GetCachedRanges(byteRanges);
if (NS_FAILED(rv)) {
return;
}
MonitorAutoLock mon(mMonitor);
mIndex->UpdateMoofIndex(byteRanges);
} }
void void

View File

@ -45,6 +45,8 @@ public:
virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override; virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override;
virtual void NotifyDataRemoved() override;
private: private:
friend class MP4TrackDemuxer; friend class MP4TrackDemuxer;
nsRefPtr<MediaResource> mResource; nsRefPtr<MediaResource> mResource;
@ -83,6 +85,8 @@ private:
friend class MP4Demuxer; friend class MP4Demuxer;
void NotifyDataArrived(); void NotifyDataArrived();
void UpdateSamples(nsTArray<nsRefPtr<MediaRawData>>& aSamples); void UpdateSamples(nsTArray<nsRefPtr<MediaRawData>>& aSamples);
void EnsureUpToDateIndex();
void SetNextKeyFrameTime();
nsRefPtr<MP4Demuxer> mParent; nsRefPtr<MP4Demuxer> mParent;
nsRefPtr<mp4_demuxer::Index> mIndex; nsRefPtr<mp4_demuxer::Index> mIndex;
UniquePtr<mp4_demuxer::SampleIterator> mIterator; UniquePtr<mp4_demuxer::SampleIterator> mIterator;
@ -91,6 +95,7 @@ private:
Maybe<media::TimeUnit> mNextKeyframeTime; Maybe<media::TimeUnit> mNextKeyframeTime;
// Queued samples extracted by the demuxer, but not yet returned. // Queued samples extracted by the demuxer, but not yet returned.
nsRefPtr<MediaRawData> mQueuedSample; nsRefPtr<MediaRawData> mQueuedSample;
bool mNeedReIndex;
// We do not actually need a monitor, however MoofParser will assert // We do not actually need a monitor, however MoofParser will assert
// if a monitor isn't held. // if a monitor isn't held.

View File

@ -709,3 +709,12 @@ TEST(IntervalSet, FooIntervalSet)
EXPECT_EQ(Foo<int>(), is[0].mStart); EXPECT_EQ(Foo<int>(), is[0].mStart);
EXPECT_EQ(Foo<int>(4,5,6), is[0].mEnd); EXPECT_EQ(Foo<int>(4,5,6), is[0].mEnd);
} }
TEST(IntervalSet, StaticAssert)
{
typedef media::IntervalSet<int> IntIntervals;
media::Interval<int> i;
static_assert(mozilla::IsSame<nsTArray_CopyChooser<IntIntervals>::Type, nsTArray_CopyWithConstructors<IntIntervals>>::value, "Must use copy constructor");
static_assert(mozilla::IsSame<nsTArray_CopyChooser<media::TimeIntervals>::Type, nsTArray_CopyWithConstructors<media::TimeIntervals>>::value, "Must use copy constructor");
}

View File

@ -8,6 +8,7 @@
#include "WebMBufferedParser.h" #include "WebMBufferedParser.h"
#include "mozilla/Endian.h" #include "mozilla/Endian.h"
#include "mozilla/ErrorResult.h"
#include "mp4_demuxer/MoofParser.h" #include "mp4_demuxer/MoofParser.h"
#include "mozilla/Logging.h" #include "mozilla/Logging.h"
#include "MediaData.h" #include "MediaData.h"
@ -324,7 +325,13 @@ public:
mp4_demuxer::Interval<mp4_demuxer::Microseconds> compositionRange = mp4_demuxer::Interval<mp4_demuxer::Microseconds> compositionRange =
mParser->GetCompositionRange(byteRanges); mParser->GetCompositionRange(byteRanges);
mResource->EvictData(mParser->mOffset, mParser->mOffset);
ErrorResult rv;
mResource->EvictData(mParser->mOffset, mParser->mOffset, rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
return false;
}
if (compositionRange.IsNull()) { if (compositionRange.IsNull()) {
return false; return false;

View File

@ -89,14 +89,15 @@ ResourceQueue::AppendItem(MediaLargeByteBuffer* aData)
} }
uint32_t uint32_t
ResourceQueue::Evict(uint64_t aOffset, uint32_t aSizeToEvict) ResourceQueue::Evict(uint64_t aOffset, uint32_t aSizeToEvict,
ErrorResult& aRv)
{ {
SBR_DEBUG("Evict(aOffset=%llu, aSizeToEvict=%u)", SBR_DEBUG("Evict(aOffset=%llu, aSizeToEvict=%u)",
aOffset, aSizeToEvict); aOffset, aSizeToEvict);
return EvictBefore(std::min(aOffset, mOffset + (uint64_t)aSizeToEvict)); return EvictBefore(std::min(aOffset, mOffset + (uint64_t)aSizeToEvict), aRv);
} }
uint32_t ResourceQueue::EvictBefore(uint64_t aOffset) uint32_t ResourceQueue::EvictBefore(uint64_t aOffset, ErrorResult& aRv)
{ {
SBR_DEBUG("EvictBefore(%llu)", aOffset); SBR_DEBUG("EvictBefore(%llu)", aOffset);
uint32_t evicted = 0; uint32_t evicted = 0;
@ -111,8 +112,12 @@ uint32_t ResourceQueue::EvictBefore(uint64_t aOffset)
mOffset += offset; mOffset += offset;
evicted += offset; evicted += offset;
nsRefPtr<MediaLargeByteBuffer> data = new MediaLargeByteBuffer; nsRefPtr<MediaLargeByteBuffer> data = new MediaLargeByteBuffer;
data->AppendElements(item->mData->Elements() + offset, if (!data->AppendElements(item->mData->Elements() + offset,
item->mData->Length() - offset); item->mData->Length() - offset)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return 0;
}
item->mData = data; item->mData = data;
break; break;
} }

View File

@ -12,6 +12,8 @@
namespace mozilla { namespace mozilla {
class ErrorResult;
// A SourceBufferResource has a queue containing the data that is appended // A SourceBufferResource has a queue containing the data that is appended
// to it. The queue holds instances of ResourceItem which is an array of the // to it. The queue holds instances of ResourceItem which is an array of the
// bytes. Appending data to the SourceBufferResource pushes this onto the // bytes. Appending data to the SourceBufferResource pushes this onto the
@ -47,9 +49,10 @@ public:
// Tries to evict at least aSizeToEvict from the queue up until // Tries to evict at least aSizeToEvict from the queue up until
// aOffset. Returns amount evicted. // aOffset. Returns amount evicted.
uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict); uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict,
ErrorResult& aRv);
uint32_t EvictBefore(uint64_t aOffset); uint32_t EvictBefore(uint64_t aOffset, ErrorResult& aRv);
uint32_t EvictAll(); uint32_t EvictAll();

View File

@ -174,12 +174,13 @@ SourceBufferResource::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCo
} }
uint32_t uint32_t
SourceBufferResource::EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold) SourceBufferResource::EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold,
ErrorResult& aRv)
{ {
SBR_DEBUG("EvictData(aPlaybackOffset=%llu," SBR_DEBUG("EvictData(aPlaybackOffset=%llu,"
"aThreshold=%u)", aPlaybackOffset, aThreshold); "aThreshold=%u)", aPlaybackOffset, aThreshold);
ReentrantMonitorAutoEnter mon(mMonitor); ReentrantMonitorAutoEnter mon(mMonitor);
uint32_t result = mInputBuffer.Evict(aPlaybackOffset, aThreshold); uint32_t result = mInputBuffer.Evict(aPlaybackOffset, aThreshold, aRv);
if (result > 0) { if (result > 0) {
// Wake up any waiting threads in case a ReadInternal call // Wake up any waiting threads in case a ReadInternal call
// is now invalid. // is now invalid.
@ -189,13 +190,13 @@ SourceBufferResource::EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold)
} }
void void
SourceBufferResource::EvictBefore(uint64_t aOffset) SourceBufferResource::EvictBefore(uint64_t aOffset, ErrorResult& aRv)
{ {
SBR_DEBUG("EvictBefore(aOffset=%llu)", aOffset); SBR_DEBUG("EvictBefore(aOffset=%llu)", aOffset);
ReentrantMonitorAutoEnter mon(mMonitor); ReentrantMonitorAutoEnter mon(mMonitor);
// If aOffset is past the current playback offset we don't evict. // If aOffset is past the current playback offset we don't evict.
if (aOffset < mOffset) { if (aOffset < mOffset) {
mInputBuffer.EvictBefore(aOffset); mInputBuffer.EvictBefore(aOffset, aRv);
} }
// Wake up any waiting threads in case a ReadInternal call // Wake up any waiting threads in case a ReadInternal call
// is now invalid. // is now invalid.

View File

@ -112,10 +112,11 @@ public:
} }
// Remove data from resource if it holds more than the threshold // Remove data from resource if it holds more than the threshold
// number of bytes. Returns amount evicted. // number of bytes. Returns amount evicted.
uint32_t EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold); uint32_t EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold,
ErrorResult& aRv);
// Remove data from resource before the given offset. // Remove data from resource before the given offset.
void EvictBefore(uint64_t aOffset); void EvictBefore(uint64_t aOffset, ErrorResult& aRv);
// Remove all data from the resource // Remove all data from the resource
uint32_t EvictAll(); uint32_t EvictAll();

View File

@ -241,7 +241,7 @@ TrackBuffer::AppendData(MediaLargeByteBuffer* aData, int64_t aTimestampOffset)
// Tell our reader that we have more data to ensure that playback starts if // Tell our reader that we have more data to ensure that playback starts if
// required when data is appended. // required when data is appended.
mParentDecoder->GetReader()->MaybeNotifyHaveData(); NotifyTimeRangesChanged();
mInitializationPromise.Resolve(gotMedia, __func__); mInitializationPromise.Resolve(gotMedia, __func__);
return p; return p;
@ -263,11 +263,20 @@ TrackBuffer::AppendDataToCurrentResource(MediaLargeByteBuffer* aData, uint32_t a
mCurrentDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData->Elements()), mCurrentDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData->Elements()),
aData->Length(), appendOffset); aData->Length(), appendOffset);
mParentDecoder->NotifyBytesDownloaded(); mParentDecoder->NotifyBytesDownloaded();
mParentDecoder->NotifyTimeRangesChanged(); NotifyTimeRangesChanged();
return true; return true;
} }
void
TrackBuffer::NotifyTimeRangesChanged()
{
RefPtr<nsIRunnable> task =
NS_NewRunnableMethod(mParentDecoder->GetReader(),
&MediaSourceReader::NotifyTimeRangesChanged);
mParentDecoder->GetReader()->GetTaskQueue()->Dispatch(task.forget());
}
class DecoderSorter class DecoderSorter
{ {
public: public:
@ -337,16 +346,23 @@ TrackBuffer::EvictData(double aPlaybackTime,
toEvict -= decoders[i]->GetResource()->EvictAll(); toEvict -= decoders[i]->GetResource()->EvictAll();
} else { } else {
int64_t playbackOffset = int64_t playbackOffset =
decoders[i]->ConvertToByteOffset(time.ToMicroseconds()); decoders[i]->ConvertToByteOffset(time.ToSeconds());
MSE_DEBUG("evicting some bufferedEnd=%f " MSE_DEBUG("evicting some bufferedEnd=%f "
"aPlaybackTime=%f time=%f, playbackOffset=%lld size=%lld", "aPlaybackTime=%f time=%f, playbackOffset=%lld size=%lld",
buffered.GetEnd().ToSeconds(), aPlaybackTime, time, buffered.GetEnd().ToSeconds(), aPlaybackTime, time,
playbackOffset, decoders[i]->GetResource()->GetSize()); playbackOffset, decoders[i]->GetResource()->GetSize());
if (playbackOffset > 0) { if (playbackOffset > 0) {
ErrorResult rv;
toEvict -= decoders[i]->GetResource()->EvictData(playbackOffset, toEvict -= decoders[i]->GetResource()->EvictData(playbackOffset,
playbackOffset); playbackOffset,
rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
return false;
}
} }
} }
decoders[i]->GetReader()->NotifyDataRemoved();
} }
} }
@ -368,6 +384,7 @@ TrackBuffer::EvictData(double aPlaybackTime,
buffered.GetStart().ToSeconds(), buffered.GetEnd().ToSeconds(), buffered.GetStart().ToSeconds(), buffered.GetEnd().ToSeconds(),
aPlaybackTime, decoders[i]->GetResource()->GetSize()); aPlaybackTime, decoders[i]->GetResource()->GetSize());
toEvict -= decoders[i]->GetResource()->EvictAll(); toEvict -= decoders[i]->GetResource()->EvictAll();
decoders[i]->GetReader()->NotifyDataRemoved();
} }
// Evict all data from future decoders, starting furthest away from // Evict all data from future decoders, starting furthest away from
@ -413,6 +430,7 @@ TrackBuffer::EvictData(double aPlaybackTime,
buffered.GetStart().ToSeconds(), buffered.GetEnd().ToSeconds(), buffered.GetStart().ToSeconds(), buffered.GetEnd().ToSeconds(),
aPlaybackTime, decoders[i]->GetResource()->GetSize()); aPlaybackTime, decoders[i]->GetResource()->GetSize());
toEvict -= decoders[i]->GetResource()->EvictAll(); toEvict -= decoders[i]->GetResource()->EvictAll();
decoders[i]->GetReader()->NotifyDataRemoved();
} }
RemoveEmptyDecoders(decoders); RemoveEmptyDecoders(decoders);
@ -429,6 +447,10 @@ TrackBuffer::EvictData(double aPlaybackTime,
} }
} }
if (evicted) {
NotifyTimeRangesChanged();
}
return evicted; return evicted;
} }
@ -490,9 +512,16 @@ TrackBuffer::EvictBefore(double aTime)
if (endOffset > 0) { if (endOffset > 0) {
MSE_DEBUG("decoder=%u offset=%lld", MSE_DEBUG("decoder=%u offset=%lld",
i, endOffset); i, endOffset);
mInitializedDecoders[i]->GetResource()->EvictBefore(endOffset); ErrorResult rv;
mInitializedDecoders[i]->GetResource()->EvictBefore(endOffset, rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
return;
}
mInitializedDecoders[i]->GetReader()->NotifyDataRemoved();
} }
} }
NotifyTimeRangesChanged();
} }
media::TimeIntervals media::TimeIntervals
@ -788,7 +817,7 @@ TrackBuffer::CompleteInitializeDecoder(SourceBufferDecoder* aDecoder)
// Tell our reader that we have more data to ensure that playback starts if // Tell our reader that we have more data to ensure that playback starts if
// required when data is appended. // required when data is appended.
mParentDecoder->GetReader()->MaybeNotifyHaveData(); NotifyTimeRangesChanged();
MSE_DEBUG("Reader %p activated", MSE_DEBUG("Reader %p activated",
aDecoder->GetReader()); aDecoder->GetReader());
@ -830,7 +859,7 @@ TrackBuffer::RegisterDecoder(SourceBufferDecoder* aDecoder)
return false; return false;
} }
mInitializedDecoders.AppendElement(aDecoder); mInitializedDecoders.AppendElement(aDecoder);
mParentDecoder->NotifyTimeRangesChanged(); NotifyTimeRangesChanged();
return true; return true;
} }
@ -1086,9 +1115,15 @@ TrackBuffer::RangeRemoval(media::TimeUnit aStart,
buffered.GetEnd().ToSeconds(), offset, buffered.GetEnd().ToSeconds(), offset,
decoders[i]->GetResource()->GetSize()); decoders[i]->GetResource()->GetSize());
if (offset > 0) { if (offset > 0) {
decoders[i]->GetResource()->EvictData(offset, offset); ErrorResult rv;
decoders[i]->GetResource()->EvictData(offset, offset, rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
return false;
}
} }
} }
decoders[i]->GetReader()->NotifyDataRemoved();
} }
} else { } else {
// Only trimming existing buffers. // Only trimming existing buffers.
@ -1099,11 +1134,13 @@ TrackBuffer::RangeRemoval(media::TimeUnit aStart,
} else { } else {
decoders[i]->Trim(aStart.ToMicroseconds()); decoders[i]->Trim(aStart.ToMicroseconds());
} }
decoders[i]->GetReader()->NotifyDataRemoved();
} }
} }
RemoveEmptyDecoders(decoders); RemoveEmptyDecoders(decoders);
NotifyTimeRangesChanged();
return true; return true;
} }

View File

@ -129,6 +129,8 @@ private:
// data is appended to the current decoder's SourceBufferResource. // data is appended to the current decoder's SourceBufferResource.
bool AppendDataToCurrentResource(MediaLargeByteBuffer* aData, bool AppendDataToCurrentResource(MediaLargeByteBuffer* aData,
uint32_t aDuration /* microseconds */); uint32_t aDuration /* microseconds */);
// Queue on the parent's decoder task queue a call to NotifyTimeRangesChanged.
void NotifyTimeRangesChanged();
// Queue execution of InitializeDecoder on mTaskQueue. // Queue execution of InitializeDecoder on mTaskQueue.
bool QueueInitializeDecoder(SourceBufferDecoder* aDecoder); bool QueueInitializeDecoder(SourceBufferDecoder* aDecoder);

View File

@ -74,7 +74,7 @@ SharedDecoderManager::SharedDecoderManager()
, mActiveProxy(nullptr) , mActiveProxy(nullptr)
, mActiveCallback(nullptr) , mActiveCallback(nullptr)
, mWaitForInternalDrain(false) , mWaitForInternalDrain(false)
, mMonitor("SharedDecoderProxy") , mMonitor("SharedDecoderManager")
, mDecoderReleasedResources(false) , mDecoderReleasedResources(false)
{ {
MOZ_ASSERT(NS_IsMainThread()); // taskqueue must be created on main thread. MOZ_ASSERT(NS_IsMainThread()); // taskqueue must be created on main thread.
@ -163,16 +163,15 @@ void
SharedDecoderManager::SetIdle(MediaDataDecoder* aProxy) SharedDecoderManager::SetIdle(MediaDataDecoder* aProxy)
{ {
if (aProxy && mActiveProxy == aProxy) { if (aProxy && mActiveProxy == aProxy) {
MonitorAutoLock mon(mMonitor);
mWaitForInternalDrain = true;
nsresult rv;
{ {
// We don't want to hold the lock while calling Drain() has some MonitorAutoLock mon(mMonitor);
mWaitForInternalDrain = true;
// We don't want to hold the lock while calling Drain() as some
// platform implementations call DrainComplete() immediately. // platform implementations call DrainComplete() immediately.
MonitorAutoUnlock mon(mMonitor);
rv = mActiveProxy->Drain();
} }
nsresult rv = mActiveProxy->Drain();
if (NS_SUCCEEDED(rv)) { if (NS_SUCCEEDED(rv)) {
MonitorAutoLock mon(mMonitor);
while (mWaitForInternalDrain) { while (mWaitForInternalDrain) {
mon.Wait(); mon.Wait();
} }

View File

@ -56,6 +56,7 @@ private:
SharedDecoderProxy* mActiveProxy; SharedDecoderProxy* mActiveProxy;
MediaDataDecoderCallback* mActiveCallback; MediaDataDecoderCallback* mActiveCallback;
nsAutoPtr<MediaDataDecoderCallback> mCallback; nsAutoPtr<MediaDataDecoderCallback> mCallback;
// access protected by mMonitor
bool mWaitForInternalDrain; bool mWaitForInternalDrain;
Monitor mMonitor; Monitor mMonitor;
bool mDecoderReleasedResources; bool mDecoderReleasedResources;

View File

@ -12,6 +12,7 @@
#include "MediaInfo.h" #include "MediaInfo.h"
#include "FFmpegH264Decoder.h" #include "FFmpegH264Decoder.h"
#include "FFmpegLog.h"
#define GECKO_FRAME_TYPE 0x00093CC0 #define GECKO_FRAME_TYPE 0x00093CC0
@ -49,6 +50,20 @@ FFmpegH264Decoder<LIBAV_VER>::Init()
return NS_OK; return NS_OK;
} }
int64_t
FFmpegH264Decoder<LIBAV_VER>::GetPts(const AVPacket& packet)
{
#if LIBAVCODEC_VERSION_MAJOR == 53
if (mFrame->pkt_pts == 0) {
return mFrame->pkt_dts;
} else {
return mFrame->pkt_pts;
}
#else
return mFrame->pkt_pts;
#endif
}
FFmpegH264Decoder<LIBAV_VER>::DecodeResult FFmpegH264Decoder<LIBAV_VER>::DecodeResult
FFmpegH264Decoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample) FFmpegH264Decoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample)
{ {
@ -68,10 +83,19 @@ FFmpegH264Decoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample)
return DecodeResult::DECODE_ERROR; return DecodeResult::DECODE_ERROR;
} }
// Required with old version of FFmpeg/LibAV
mFrame->reordered_opaque = AV_NOPTS_VALUE;
int decoded; int decoded;
int bytesConsumed = int bytesConsumed =
avcodec_decode_video2(mCodecContext, mFrame, &decoded, &packet); avcodec_decode_video2(mCodecContext, mFrame, &decoded, &packet);
FFMPEG_LOG("DoDecodeFrame:decode_video: rv=%d decoded=%d "
"(Input: pts(%lld) dts(%lld) Output: pts(%lld) "
"opaque(%lld) pkt_pts(%lld) pkt_dts(%lld))",
bytesConsumed, decoded, packet.pts, packet.dts, mFrame->pts,
mFrame->reordered_opaque, mFrame->pkt_pts, mFrame->pkt_dts);
if (bytesConsumed < 0) { if (bytesConsumed < 0) {
NS_WARNING("FFmpeg video decoder error."); NS_WARNING("FFmpeg video decoder error.");
mCallback->Error(); mCallback->Error();
@ -80,6 +104,10 @@ FFmpegH264Decoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample)
// If we've decoded a frame then we need to output it // If we've decoded a frame then we need to output it
if (decoded) { if (decoded) {
int64_t pts = GetPts(packet);
FFMPEG_LOG("Got one frame output with pts=%lld opaque=%lld",
pts, mCodecContext->reordered_opaque);
VideoInfo info; VideoInfo info;
info.mDisplay = nsIntSize(mDisplayWidth, mDisplayHeight); info.mDisplay = nsIntSize(mDisplayWidth, mDisplayHeight);
@ -105,7 +133,7 @@ FFmpegH264Decoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample)
nsRefPtr<VideoData> v = VideoData::Create(info, nsRefPtr<VideoData> v = VideoData::Create(info,
mImageContainer, mImageContainer,
aSample->mOffset, aSample->mOffset,
mFrame->pkt_pts, pts,
aSample->mDuration, aSample->mDuration,
b, b,
aSample->mKeyframe, aSample->mKeyframe,

View File

@ -59,6 +59,7 @@ private:
static int AllocateBufferCb(AVCodecContext* aCodecContext, AVFrame* aFrame); static int AllocateBufferCb(AVCodecContext* aCodecContext, AVFrame* aFrame);
static void ReleaseBufferCb(AVCodecContext* aCodecContext, AVFrame* aFrame); static void ReleaseBufferCb(AVCodecContext* aCodecContext, AVFrame* aFrame);
int64_t GetPts(const AVPacket& packet);
MediaDataDecoderCallback* mCallback; MediaDataDecoderCallback* mCallback;
nsRefPtr<ImageContainer> mImageContainer; nsRefPtr<ImageContainer> mImageContainer;

View File

@ -205,6 +205,7 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset,
aOutData = nullptr; aOutData = nullptr;
RefPtr<IMFSample> sample; RefPtr<IMFSample> sample;
HRESULT hr; HRESULT hr;
int typeChangeCount = 0;
while (true) { while (true) {
hr = mDecoder->Output(&sample); hr = mDecoder->Output(&sample);
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
@ -213,6 +214,11 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset,
if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
hr = UpdateOutputType(); hr = UpdateOutputType();
NS_ENSURE_TRUE(SUCCEEDED(hr), hr); NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
// Catch infinite loops, but some decoders perform at least 2 stream
// changes on consecutive calls, so be permissive.
// 100 is arbitrarily > 2.
NS_ENSURE_TRUE(typeChangeCount < 100, MF_E_TRANSFORM_STREAM_CHANGE);
++typeChangeCount;
continue; continue;
} }
break; break;

View File

@ -482,6 +482,7 @@ WMFVideoMFTManager::Output(int64_t aStreamOffset,
RefPtr<IMFSample> sample; RefPtr<IMFSample> sample;
HRESULT hr; HRESULT hr;
aOutData = nullptr; aOutData = nullptr;
int typeChangeCount = 0;
// Loop until we decode a sample, or an unexpected error that we can't // Loop until we decode a sample, or an unexpected error that we can't
// handle occurs. // handle occurs.
@ -497,7 +498,12 @@ WMFVideoMFTManager::Output(int64_t aStreamOffset,
MOZ_ASSERT(!sample); MOZ_ASSERT(!sample);
hr = ConfigureVideoFrameGeometry(); hr = ConfigureVideoFrameGeometry();
NS_ENSURE_TRUE(SUCCEEDED(hr), hr); NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
// Catch infinite loops, but some decoders perform at least 2 stream
// changes on consecutive calls, so be permissive.
// 100 is arbitrarily > 2.
NS_ENSURE_TRUE(typeChangeCount < 100, MF_E_TRANSFORM_STREAM_CHANGE);
// Loop back and try decoding again... // Loop back and try decoding again...
++typeChangeCount;
continue; continue;
} }
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {

Binary file not shown.

View File

@ -0,0 +1 @@
Cache-Control: no-store

View File

@ -227,6 +227,9 @@ var gPlayTests = [
{ name:"test-8-7.1.opus", type:"audio/ogg; codecs=opus", duration:13.478 }, { name:"test-8-7.1.opus", type:"audio/ogg; codecs=opus", duration:13.478 },
{ name:"gizmo.mp4", type:"video/mp4", duration:5.56 }, { name:"gizmo.mp4", type:"video/mp4", duration:5.56 },
// Test playback of a MP4 file with a non-zero start time (and audio starting
// a second later).
{ name:"bipbop-lateaudio.mp4", type:"video/mp4", duration:2.401 },
{ name:"small-shot.m4a", type:"audio/mp4", duration:0.29 }, { name:"small-shot.m4a", type:"audio/mp4", duration:0.29 },
{ name:"small-shot.mp3", type:"audio/mpeg", duration:0.27 }, { name:"small-shot.mp3", type:"audio/mpeg", duration:0.27 },

View File

@ -66,6 +66,8 @@ support-files =
bipbop-cenc1-video1.m4s bipbop-cenc1-video1.m4s
bipbop-cenc1-video2.m4s bipbop-cenc1-video2.m4s
bipbop-cenc1-videoinit.mp4 bipbop-cenc1-videoinit.mp4
bipbop-lateaudio.mp4
bipbop-lateaudio.mp4^headers^
bogus.duh bogus.duh
bogus.ogv bogus.ogv
bogus.ogv^headers^ bogus.ogv^headers^

View File

@ -32,6 +32,7 @@
var audiotrack; var audiotrack;
return navigator.mediaDevices.getUserMedia({video:true, audio:true, fake:true}) return navigator.mediaDevices.getUserMedia({video:true, audio:true, fake:true})
.then(newStream => { .then(newStream => {
window.grip = newStream;
newTrack = newStream.getVideoTracks()[0]; newTrack = newStream.getVideoTracks()[0];
audiotrack = newStream.getAudioTracks()[0]; audiotrack = newStream.getAudioTracks()[0];
isnot(newTrack, sender.track, "replacing with a different track"); isnot(newTrack, sender.track, "replacing with a different track");

View File

@ -599,13 +599,17 @@ MobileMessageManager::DispatchTrustedDeletedEventToSelf(nsISupports* aDeletedInf
uint32_t msgIdLength = info->GetData().deletedMessageIds().Length(); uint32_t msgIdLength = info->GetData().deletedMessageIds().Length();
if (msgIdLength) { if (msgIdLength) {
Sequence<int32_t>& deletedMsgIds = init.mDeletedMessageIds.SetValue(); Sequence<int32_t>& deletedMsgIds = init.mDeletedMessageIds.SetValue();
deletedMsgIds.AppendElements(info->GetData().deletedMessageIds()); if (!deletedMsgIds.AppendElements(info->GetData().deletedMessageIds())) {
return NS_ERROR_OUT_OF_MEMORY;
}
} }
uint32_t threadIdLength = info->GetData().deletedThreadIds().Length(); uint32_t threadIdLength = info->GetData().deletedThreadIds().Length();
if (threadIdLength) { if (threadIdLength) {
Sequence<uint64_t>& deletedThreadIds = init.mDeletedThreadIds.SetValue(); Sequence<uint64_t>& deletedThreadIds = init.mDeletedThreadIds.SetValue();
deletedThreadIds.AppendElements(info->GetData().deletedThreadIds()); if (!deletedThreadIds.AppendElements(info->GetData().deletedThreadIds())) {
return NS_ERROR_OUT_OF_MEMORY;
}
} }
nsRefPtr<MozMessageDeletedEvent> event = nsRefPtr<MozMessageDeletedEvent> event =

View File

@ -92,7 +92,7 @@ interface CanvasRenderingContext2D {
void fill(Path2D path, optional CanvasWindingRule winding = "nonzero"); void fill(Path2D path, optional CanvasWindingRule winding = "nonzero");
void stroke(); void stroke();
void stroke(Path2D path); void stroke(Path2D path);
[Pref="canvas.focusring.enabled"] void drawFocusIfNeeded(Element element); [Pref="canvas.focusring.enabled", Throws] void drawFocusIfNeeded(Element element);
// NOT IMPLEMENTED void drawSystemFocusRing(Path path, HTMLElement element); // NOT IMPLEMENTED void drawSystemFocusRing(Path path, HTMLElement element);
[Pref="canvas.customfocusring.enabled"] boolean drawCustomFocusRing(Element element); [Pref="canvas.customfocusring.enabled"] boolean drawCustomFocusRing(Element element);
// NOT IMPLEMENTED boolean drawCustomFocusRing(Path path, HTMLElement element); // NOT IMPLEMENTED boolean drawCustomFocusRing(Path path, HTMLElement element);
@ -246,7 +246,7 @@ interface CanvasDrawingStyles {
attribute double miterLimit; // (default 10) attribute double miterLimit; // (default 10)
// dashed lines // dashed lines
[LenientFloat] void setLineDash(sequence<double> segments); // default empty [LenientFloat, Throws] void setLineDash(sequence<double> segments); // default empty
sequence<double> getLineDash(); sequence<double> getLineDash();
[LenientFloat] attribute double lineDashOffset; [LenientFloat] attribute double lineDashOffset;

Some files were not shown because too many files have changed in this diff Show More