Merge f-t to m-c, a=merge

This commit is contained in:
Phil Ringnalda 2015-05-02 10:13:08 -07:00
commit 7c18683a54
28 changed files with 659 additions and 382 deletions

View File

@ -717,6 +717,10 @@ Toolbox.prototype = {
this._buildPickerButton();
}
// Set the visibility of the built in buttons before adding more buttons
// so they are shown before calling into the GCLI actor.
this.setToolboxButtonsVisibility();
const options = {
environment: CommandUtils.createEnvironment(this, '_target')
};

View File

@ -0,0 +1,293 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Task } = require("resource://gre/modules/Task.jsm");
const { Promise } = require("resource://gre/modules/Promise.jsm");
const {
actorCompatibilityBridge, getProfiler,
MockMemoryFront, MockTimelineFront,
memoryActorSupported, timelineActorSupported
} = require("devtools/performance/compatibility");
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
loader.lazyRequireGetter(this, "RecordingUtils",
"devtools/performance/recording-utils", true);
loader.lazyRequireGetter(this, "TimelineFront",
"devtools/server/actors/timeline", true);
loader.lazyRequireGetter(this, "MemoryFront",
"devtools/server/actors/memory", true);
loader.lazyRequireGetter(this, "timers",
"resource://gre/modules/Timer.jsm");
// how often do we pull allocation sites from the memory actor
const ALLOCATION_SITE_POLL_TIMER = 200; // ms
const MEMORY_ACTOR_METHODS = [
"destroy", "attach", "detach", "getState", "getAllocationsSettings",
"getAllocations", "startRecordingAllocations", "stopRecordingAllocations"
];
const TIMELINE_ACTOR_METHODS = [
"start", "stop",
];
const PROFILER_ACTOR_METHODS = [
"isActive", "startProfiler", "getStartOptions", "stopProfiler",
"registerEventNotifications", "unregisterEventNotifications"
];
/**
* Constructor for a facade around an underlying ProfilerFront.
*/
function ProfilerFrontFacade (target) {
this._target = target;
this._onProfilerEvent = this._onProfilerEvent.bind(this);
EventEmitter.decorate(this);
}
ProfilerFrontFacade.prototype = {
EVENTS: ["console-api-profiler", "profiler-stopped"],
// Connects to the targets underlying real ProfilerFront.
connect: Task.async(function*() {
let target = this._target;
this._actor = yield getProfiler(target);
// Fetch and store information about the SPS profiler and
// server profiler.
this.traits = {};
this.traits.filterable = target.getTrait("profilerDataFilterable");
// Directly register to event notifications when connected
// to hook into `console.profile|profileEnd` calls.
yield this.registerEventNotifications({ events: this.EVENTS });
// TODO bug 1159389, listen directly to actor if supporting new front
target.client.addListener("eventNotification", this._onProfilerEvent);
}),
/**
* Unregisters events for the underlying profiler actor.
*/
destroy: Task.async(function *() {
yield this.unregisterEventNotifications({ events: this.EVENTS });
// TODO bug 1159389, listen directly to actor if supporting new front
this._target.client.removeListener("eventNotification", this._onProfilerEvent);
}),
/**
* Starts the profiler actor, if necessary.
*/
start: Task.async(function *(options={}) {
// Start the profiler only if it wasn't already active. The built-in
// nsIPerformance module will be kept recording, because it's the same instance
// for all targets and interacts with the whole platform, so we don't want
// to affect other clients by stopping (or restarting) it.
let profilerStatus = yield this.isActive();
if (profilerStatus.isActive) {
this.emit("profiler-already-active");
return profilerStatus.currentTime;
}
// Translate options from the recording model into profiler-specific
// options for the nsIProfiler
let profilerOptions = {
entries: options.bufferSize,
interval: options.sampleFrequency ? (1000 / (options.sampleFrequency * 1000)) : void 0
};
yield this.startProfiler(profilerOptions);
this.emit("profiler-activated");
return 0;
}),
/**
* Returns profile data from now since `startTime`.
*/
getProfile: Task.async(function *(options) {
let profilerData = yield (actorCompatibilityBridge("getProfile").call(this, options));
// If the backend does not support filtering by start and endtime on platform (< Fx40),
// do it on the client (much slower).
if (!this.traits.filterable) {
RecordingUtils.filterSamples(profilerData.profile, options.startTime || 0);
}
return profilerData;
}),
/**
* Invoked whenever a registered event was emitted by the profiler actor.
*
* @param object response
* The data received from the backend.
*/
_onProfilerEvent: function (_, { topic, subject, details }) {
if (topic === "console-api-profiler") {
if (subject.action === "profile") {
this.emit("console-profile-start", details);
} else if (subject.action === "profileEnd") {
this.emit("console-profile-end", details);
}
} else if (topic === "profiler-stopped") {
this.emit("profiler-stopped");
}
},
toString: () => "[object ProfilerFrontFacade]"
};
// Bind all the methods that directly proxy to the actor
PROFILER_ACTOR_METHODS.forEach(method => ProfilerFrontFacade.prototype[method] = actorCompatibilityBridge(method));
exports.ProfilerFront = ProfilerFrontFacade;
/**
* Constructor for a facade around an underlying TimelineFront.
*/
function TimelineFrontFacade (target) {
this._target = target;
EventEmitter.decorate(this);
}
TimelineFrontFacade.prototype = {
EVENTS: ["markers", "frames", "memory", "ticks"],
connect: Task.async(function*() {
let supported = yield timelineActorSupported(this._target);
this._actor = supported ?
new TimelineFront(this._target.client, this._target.form) :
new MockTimelineFront();
this.IS_MOCK = !supported;
// Binds underlying actor events and consolidates them to a `timeline-data`
// exposed event.
this.EVENTS.forEach(type => {
let handler = this[`_on${type}`] = this._onTimelineData.bind(this, type);
this._actor.on(type, handler);
});
}),
/**
* Override actor's destroy, so we can unregister listeners before
* destroying the underlying actor.
*/
destroy: Task.async(function *() {
this.EVENTS.forEach(type => this._actor.off(type, this[`_on${type}`]));
yield this._actor.destroy();
}),
/**
* An aggregate of all events (markers, frames, memory, ticks) and exposes
* to PerformanceActorsConnection as a single event.
*/
_onTimelineData: function (type, ...data) {
this.emit("timeline-data", type, ...data);
},
toString: () => "[object TimelineFrontFacade]"
};
// Bind all the methods that directly proxy to the actor
TIMELINE_ACTOR_METHODS.forEach(method => TimelineFrontFacade.prototype[method] = actorCompatibilityBridge(method));
exports.TimelineFront = TimelineFrontFacade;
/**
* Constructor for a facade around an underlying ProfilerFront.
*/
function MemoryFrontFacade (target) {
this._target = target;
this._pullAllocationSites = this._pullAllocationSites.bind(this);
EventEmitter.decorate(this);
}
MemoryFrontFacade.prototype = {
connect: Task.async(function*() {
let supported = yield memoryActorSupported(this._target);
this._actor = supported ?
new MemoryFront(this._target.client, this._target.form) :
new MockMemoryFront();
this.IS_MOCK = !supported;
}),
/**
* Starts polling for allocation information.
*/
start: Task.async(function *(options) {
if (!options.withAllocations) {
return 0;
}
yield this.attach();
let startTime = yield this.startRecordingAllocations({
probability: options.allocationsSampleProbability,
maxLogLength: options.allocationsMaxLogLength
});
yield this._pullAllocationSites();
return startTime;
}),
/**
* Stops polling for allocation information.
*/
stop: Task.async(function *(options) {
if (!options.withAllocations) {
return 0;
}
// Since `_pullAllocationSites` is usually running inside a timeout, and
// it's performing asynchronous requests to the server, a recording may
// be stopped before that method finishes executing. Therefore, we need to
// wait for the last request to `getAllocations` to finish before actually
// stopping recording allocations.
yield this._lastPullAllocationSitesFinished;
timers.clearTimeout(this._sitesPullTimeout);
let endTime = yield this.stopRecordingAllocations();
yield this.detach();
return endTime;
}),
/**
* At regular intervals, pull allocations from the memory actor, and
* forward them on this Front facade as "timeline-data" events. This
* gives the illusion that the MemoryActor supports an EventEmitter-style
* event stream.
*/
_pullAllocationSites: Task.async(function *() {
let { promise, resolve } = Promise.defer();
this._lastPullAllocationSitesFinished = promise;
if ((yield this.getState()) !== "attached") {
resolve();
return;
}
let memoryData = yield this.getAllocations();
// Match the signature of the TimelineFront events, with "timeline-data"
// being the event name, and the second argument describing the type.
this.emit("timeline-data", "allocations", {
sites: memoryData.allocations,
timestamps: memoryData.allocationsTimestamps,
frames: memoryData.frames,
counts: memoryData.counts
});
this._sitesPullTimeout = timers.setTimeout(this._pullAllocationSites, ALLOCATION_SITE_POLL_TIMER);
resolve();
}),
toString: () => "[object MemoryFrontFacade]"
};
// Bind all the methods that directly proxy to the actor
MEMORY_ACTOR_METHODS.forEach(method => MemoryFrontFacade.prototype[method] = actorCompatibilityBridge(method));
exports.MemoryFront = MemoryFrontFacade;

View File

@ -4,72 +4,9 @@
"use strict";
const { Task } = require("resource://gre/modules/Task.jsm");
loader.lazyRequireGetter(this, "promise");
const { Promise } = require("resource://gre/modules/Promise.jsm");
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
loader.lazyRequireGetter(this, "RecordingUtils",
"devtools/performance/recording-utils", true);
const REQUIRED_MEMORY_ACTOR_METHODS = [
"attach", "detach", "startRecordingAllocations", "stopRecordingAllocations", "getAllocations"
];
/**
* Constructor for a facade around an underlying ProfilerFront.
*/
function ProfilerFront (target) {
this._target = target;
}
ProfilerFront.prototype = {
// Connects to the targets underlying real ProfilerFront.
connect: Task.async(function*() {
let target = this._target;
// Chrome and content process targets already have obtained a reference
// to the profiler tab actor. Use it immediately.
if (target.form && target.form.profilerActor) {
this._profiler = target.form.profilerActor;
}
// Check if we already have a grip to the `listTabs` response object
// and, if we do, use it to get to the profiler actor.
else if (target.root && target.root.profilerActor) {
this._profiler = target.root.profilerActor;
}
// Otherwise, call `listTabs`.
else {
this._profiler = (yield listTabs(target.client)).profilerActor;
}
// Fetch and store information about the SPS profiler and
// server profiler.
this.traits = {};
this.traits.filterable = target.getTrait("profilerDataFilterable");
}),
/**
* Makes a request to the underlying real profiler actor. Handles
* backwards compatibility differences based off of the features
* and traits of the actor.
*/
_request: function (method, ...args) {
let deferred = promise.defer();
let data = args[0] || {};
data.to = this._profiler;
data.type = method;
this._target.client.request(data, res => {
// If the backend does not support filtering by start and endtime on platform (< Fx40),
// do it on the client (much slower).
if (method === "getProfile" && !this.traits.filterable) {
RecordingUtils.filterSamples(res.profile, data.startTime || 0);
}
deferred.resolve(res);
});
return deferred.promise;
}
};
exports.ProfilerFront = ProfilerFront;
/**
* A dummy front decorated with the provided methods.
@ -87,7 +24,8 @@ function MockFront (blueprint) {
function MockMemoryFront () {
MockFront.call(this, [
["initialize"],
["start", 0], // for facade
["stop", 0], // for facade
["destroy"],
["attach"],
["detach"],
@ -101,7 +39,6 @@ exports.MockMemoryFront = MockMemoryFront;
function MockTimelineFront () {
MockFront.call(this, [
["initialize"],
["destroy"],
["start", 0],
["stop", 0],
@ -169,12 +106,66 @@ function timelineActorSupported(target) {
exports.timelineActorSupported = Task.async(timelineActorSupported);
/**
* Returns a promise resolved with a listing of all the tabs in the
* provided thread client.
* Returns a promise resolving to the location of the profiler actor
* within this context.
*
* @param {TabTarget} target
* @return {Promise<ProfilerActor>}
*/
function listTabs(client) {
let deferred = promise.defer();
client.listTabs(deferred.resolve);
return deferred.promise;
function getProfiler (target) {
let { promise, resolve } = Promise.defer();
// Chrome and content process targets already have obtained a reference
// to the profiler tab actor. Use it immediately.
if (target.form && target.form.profilerActor) {
resolve(target.form.profilerActor);
}
// Check if we already have a grip to the `listTabs` response object
// and, if we do, use it to get to the profiler actor.
else if (target.root && target.root.profilerActor) {
resolve(target.root.profilerActor);
}
// Otherwise, call `listTabs`.
else {
target.client.listTabs(({ profilerActor }) => resolve(profilerActor));
}
return promise;
}
exports.getProfiler = Task.async(getProfiler);
/**
* Makes a request to an actor that does not have the modern `Front`
* interface.
*/
function legacyRequest (target, actor, method, args) {
let { promise, resolve } = Promise.defer();
let data = args[0] || {};
data.to = actor;
data.type = method;
target.client.request(data, resolve);
return promise;
}
/**
* Returns a function to be used as a method on an "Actor" in ./actors.
* Calls the underlying actor's method, supporting the modern `Front`
* interface if possible, otherwise, falling back to using
* `legacyRequest`.
*/
function actorCompatibilityBridge (method) {
return function () {
// Check to see if this is a modern ActorFront, which has its
// own `request` method. Also, check if its a mock actor, as it mimicks
// the ActorFront interface.
// The profiler actor does not currently support the modern `Front`
// interface, so we have to manually push packets to it.
// TODO bug 1159389, fix up profiler actor to not need this, however
// we will need it for backwards compat
if (this.IS_MOCK || this._actor.request) {
return this._actor[method].apply(this._actor, arguments);
}
else {
return legacyRequest(this._target, this._actor, method, arguments);
}
};
}
exports.actorCompatibilityBridge = actorCompatibilityBridge;

View File

@ -9,24 +9,15 @@ const { extend } = require("sdk/util/object");
const { RecordingModel } = require("devtools/performance/recording-model");
loader.lazyRequireGetter(this, "Services");
loader.lazyRequireGetter(this, "promise");
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
loader.lazyRequireGetter(this, "TimelineFront",
"devtools/server/actors/timeline", true);
loader.lazyRequireGetter(this, "MemoryFront",
"devtools/server/actors/memory", true);
loader.lazyRequireGetter(this, "DevToolsUtils",
"devtools/toolkit/DevToolsUtils");
loader.lazyRequireGetter(this, "compatibility",
"devtools/performance/compatibility");
loader.lazyRequireGetter(this, "actors",
"devtools/performance/actors");
loader.lazyImporter(this, "gDevTools",
"resource:///modules/devtools/gDevTools.jsm");
loader.lazyImporter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
loader.lazyImporter(this, "clearTimeout",
"resource://gre/modules/Timer.jsm");
loader.lazyImporter(this, "Promise",
"resource://gre/modules/Promise.jsm");
@ -41,9 +32,6 @@ const CONNECTION_PIPE_EVENTS = [
"recording-started", "recording-stopped"
];
// Events to listen to from the profiler actor
const PROFILER_EVENTS = ["console-api-profiler", "profiler-stopped"];
/**
* A cache of all PerformanceActorsConnection instances.
* The keys are Target objects.
@ -84,17 +72,15 @@ function PerformanceActorsConnection(target) {
this._target = target;
this._client = this._target.client;
this._request = this._request.bind(this);
this._pendingConsoleRecordings = [];
this._sitesPullTimeout = 0;
this._recordings = [];
this._onTimelineMarkers = this._onTimelineMarkers.bind(this);
this._onTimelineFrames = this._onTimelineFrames.bind(this);
this._onTimelineMemory = this._onTimelineMemory.bind(this);
this._onTimelineTicks = this._onTimelineTicks.bind(this);
this._onProfilerEvent = this._onProfilerEvent.bind(this);
this._pullAllocationSites = this._pullAllocationSites.bind(this);
this._pipeToConnection = this._pipeToConnection.bind(this);
this._onTimelineData = this._onTimelineData.bind(this);
this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this);
this._onConsoleProfileEnd = this._onConsoleProfileEnd.bind(this);
this._onProfilerUnexpectedlyStopped = this._onProfilerUnexpectedlyStopped.bind(this);
Services.obs.notifyObservers(null, "performance-actors-connection-created", null);
}
@ -128,10 +114,7 @@ PerformanceActorsConnection.prototype = {
// Only initialize the timeline and memory fronts if the respective actors
// are available. Older Gecko versions don't have existing implementations,
// in which case all the methods we need can be easily mocked.
yield this._connectProfilerActor();
yield this._connectTimelineActor();
yield this._connectMemoryActor();
yield this._connectActors();
yield this._registerListeners();
this._connected = true;
@ -159,132 +142,64 @@ PerformanceActorsConnection.prototype = {
}),
/**
* Initializes a connection to the profiler actor. Uses a facade around the ProfilerFront
* for similarity to the other actors in the shared connection.
* Initializes fronts and connects to the underlying actors using the facades
* found in ./actors.js.
*/
_connectProfilerActor: Task.async(function*() {
this._profiler = new compatibility.ProfilerFront(this._target);
yield this._profiler.connect();
}),
_connectActors: Task.async(function*() {
this._profiler = new actors.ProfilerFront(this._target);
this._memory = new actors.MemoryFront(this._target);
this._timeline = new actors.TimelineFront(this._target);
/**
* Initializes a connection to a timeline actor.
*/
_connectTimelineActor: function() {
let supported = yield compatibility.timelineActorSupported(this._target);
if (supported) {
this._timeline = new TimelineFront(this._target.client, this._target.form);
} else {
this._timeline = new compatibility.MockTimelineFront();
}
this._timelineSupported = supported;
},
yield Promise.all([
this._profiler.connect(),
this._memory.connect(),
this._timeline.connect()
]);
/**
* Initializes a connection to a memory actor.
*/
_connectMemoryActor: Task.async(function* () {
let supported = yield compatibility.memoryActorSupported(this._target);
if (supported) {
this._memory = new MemoryFront(this._target.client, this._target.form);
} else {
this._memory = new compatibility.MockMemoryFront();
}
this._memorySupported = supported;
// Expose server support status of underlying actors
// after connecting.
this._memorySupported = !this._memory.IS_MOCK;
this._timelineSupported = !this._timeline.IS_MOCK;
}),
/**
* Registers listeners on events from the underlying
* actors, so the connection can handle them.
*/
_registerListeners: Task.async(function*() {
// Pipe events from TimelineActor to the PerformanceFront
this._timeline.on("markers", this._onTimelineMarkers);
this._timeline.on("frames", this._onTimelineFrames);
this._timeline.on("memory", this._onTimelineMemory);
this._timeline.on("ticks", this._onTimelineTicks);
// Register events on the profiler actor to hook into `console.profile*` calls.
yield this._request("profiler", "registerEventNotifications", { events: PROFILER_EVENTS });
this._client.addListener("eventNotification", this._onProfilerEvent);
}),
_registerListeners: function () {
this._timeline.on("timeline-data", this._onTimelineData);
this._memory.on("timeline-data", this._onTimelineData);
this._profiler.on("console-profile-start", this._onConsoleProfileStart);
this._profiler.on("console-profile-end", this._onConsoleProfileEnd);
this._profiler.on("profiler-stopped", this._onProfilerUnexpectedlyStopped);
this._profiler.on("profiler-already-active", this._pipeToConnection);
this._profiler.on("profiler-activated", this._pipeToConnection);
},
/**
* Unregisters listeners on events on the underlying actors.
*/
_unregisterListeners: Task.async(function*() {
this._timeline.off("markers", this._onTimelineMarkers);
this._timeline.off("frames", this._onTimelineFrames);
this._timeline.off("memory", this._onTimelineMemory);
this._timeline.off("ticks", this._onTimelineTicks);
yield this._request("profiler", "unregisterEventNotifications", { events: PROFILER_EVENTS });
this._client.removeListener("eventNotification", this._onProfilerEvent);
}),
_unregisterListeners: function () {
this._timeline.off("timeline-data", this._onTimelineData);
this._memory.off("timeline-data", this._onTimelineData);
this._profiler.off("console-profile-start", this._onConsoleProfileStart);
this._profiler.off("console-profile-end", this._onConsoleProfileEnd);
this._profiler.off("profiler-stopped", this._onProfilerUnexpectedlyStopped);
this._profiler.off("profiler-already-active", this._pipeToConnection);
this._profiler.off("profiler-activated", this._pipeToConnection);
},
/**
* Closes the connections to non-profiler actors.
*/
_disconnectActors: Task.async(function* () {
yield this._timeline.destroy();
yield this._memory.destroy();
yield Promise.all([
this._profiler.destroy(),
this._timeline.destroy(),
this._memory.destroy()
]);
}),
/**
* Sends the request over the remote debugging protocol to the
* specified actor.
*
* @param string actor
* Currently supported: "profiler", "timeline", "memory".
* @param string method
* Method to call on the backend.
* @param any args [optional]
* Additional data or arguments to send with the request.
* @return object
* A promise resolved with the response once the request finishes.
*/
_request: function(actor, method, ...args) {
// Handle requests to the profiler actor.
if (actor == "profiler") {
return this._profiler._request(method, ...args);
}
// Handle requests to the timeline actor.
if (actor == "timeline") {
return this._timeline[method].apply(this._timeline, args);
}
// Handle requests to the memory actor.
if (actor == "memory") {
return this._memory[method].apply(this._memory, args);
}
},
/**
* Invoked whenever a registered event was emitted by the profiler actor.
*
* @param object response
* The data received from the backend.
*/
_onProfilerEvent: function (_, { topic, subject, details }) {
if (topic === "console-api-profiler") {
if (subject.action === "profile") {
this._onConsoleProfileStart(details);
} else if (subject.action === "profileEnd") {
this._onConsoleProfileEnd(details);
}
} else if (topic === "profiler-stopped") {
this._onProfilerUnexpectedlyStopped();
}
},
/**
* TODO handle bug 1144438
*/
_onProfilerUnexpectedlyStopped: function () {
},
/**
* Invoked whenever `console.profile` is called.
*
@ -294,7 +209,7 @@ PerformanceActorsConnection.prototype = {
* The time (in milliseconds) when the call was made, relative to when
* the nsIProfiler module was started.
*/
_onConsoleProfileStart: Task.async(function *({ profileLabel, currentTime: startTime }) {
_onConsoleProfileStart: Task.async(function *(_, { profileLabel, currentTime: startTime }) {
let recordings = this._recordings;
// Abort if a profile with this label already exists.
@ -325,7 +240,7 @@ PerformanceActorsConnection.prototype = {
* The time (in milliseconds) when the call was made, relative to when
* the nsIProfiler module was started.
*/
_onConsoleProfileEnd: Task.async(function *(data) {
_onConsoleProfileEnd: Task.async(function *(_, data) {
// If no data, abort; can occur if profiler isn't running and we get a surprise
// call to console.profileEnd()
if (!data) {
@ -361,14 +276,12 @@ PerformanceActorsConnection.prototype = {
this.emit("console-profile-end", model);
}),
/**
* Handlers for TimelineActor events. All pipe to `_onTimelineData`
* with the appropriate event name.
*/
_onTimelineMarkers: function (markers) { this._onTimelineData("markers", markers); },
_onTimelineFrames: function (delta, frames) { this._onTimelineData("frames", delta, frames); },
_onTimelineMemory: function (delta, measurement) { this._onTimelineData("memory", delta, measurement); },
_onTimelineTicks: function (delta, timestamps) { this._onTimelineData("ticks", delta, timestamps); },
/**
* TODO handle bug 1144438
*/
_onProfilerUnexpectedlyStopped: function () {
Cu.reportError("Profiler unexpectedly stopped.", arguments);
},
/**
* Called whenever there is timeline data of any of the following types:
@ -380,8 +293,7 @@ PerformanceActorsConnection.prototype = {
*
* Populate our internal store of recordings for all currently recording sessions.
*/
_onTimelineData: function (...data) {
_onTimelineData: function (_, ...data) {
this._recordings.forEach(e => e.addTimelineData.apply(e, data));
this.emit("timeline-data", ...data);
},
@ -399,15 +311,13 @@ PerformanceActorsConnection.prototype = {
let model = new RecordingModel(options);
// All actors are started asynchronously over the remote debugging protocol.
// Get the corresponding start times from each one of them.
let profilerStartTime = yield this._startProfiler(options);
let timelineStartTime = yield this._startTimeline(options);
let memoryStartTime = yield this._startMemory(options);
// The timeline and memory actors are target-dependent, so start those as well,
// even though these are mocked in older Geckos (FF < 35)
let profilerStartTime = yield this._profiler.start(options);
let timelineStartTime = yield this._timeline.start(options);
let memoryStartTime = yield this._memory.start(options);
let data = {
profilerStartTime,
timelineStartTime,
memoryStartTime
};
let data = { profilerStartTime, timelineStartTime, memoryStartTime };
// Signify to the model that the recording has started,
// populate with data and store the recording model here.
@ -445,7 +355,7 @@ PerformanceActorsConnection.prototype = {
let config = model.getConfiguration();
let startTime = model.getProfilerStartTime();
let profilerData = yield this._request("profiler", "getProfile", { startTime });
let profilerData = yield this._profiler.getProfile({ startTime });
let memoryEndTime = Date.now();
let timelineEndTime = Date.now();
@ -454,8 +364,8 @@ PerformanceActorsConnection.prototype = {
// juse use Date.now() for the memory and timeline end times, as those
// are only used in tests.
if (!this.isRecording()) {
memoryEndTime = yield this._stopMemory(config);
timelineEndTime = yield this._stopTimeline(config);
memoryEndTime = yield this._memory.stop(config);
timelineEndTime = yield this._timeline.stop(config);
}
// Set the results on the RecordingModel itself.
@ -484,127 +394,12 @@ PerformanceActorsConnection.prototype = {
},
/**
* Starts the profiler actor, if necessary.
* An event from an underlying actor that we just want
* to pipe to the connection itself.
*/
_startProfiler: Task.async(function *(options={}) {
// Start the profiler only if it wasn't already active. The built-in
// nsIPerformance module will be kept recording, because it's the same instance
// for all targets and interacts with the whole platform, so we don't want
// to affect other clients by stopping (or restarting) it.
let profilerStatus = yield this._request("profiler", "isActive");
if (profilerStatus.isActive) {
this.emit("profiler-already-active");
return profilerStatus.currentTime;
}
// Translate options from the recording model into profiler-specific
// options for the nsIProfiler
let profilerOptions = {
entries: options.bufferSize,
interval: options.sampleFrequency ? (1000 / (options.sampleFrequency * 1000)) : void 0
};
yield this._request("profiler", "startProfiler", profilerOptions);
this.emit("profiler-activated");
return 0;
}),
/**
* Starts the timeline actor.
*/
_startTimeline: Task.async(function *(options) {
// The timeline actor is target-dependent, so just make sure it's recording.
// It won't, however, be available in older Geckos (FF < 35).
return (yield this._request("timeline", "start", options));
}),
/**
* Stops the timeline actor.
*/
_stopTimeline: Task.async(function *(options) {
return (yield this._request("timeline", "stop"));
}),
/**
* Starts polling for allocations from the memory actor, if necessary.
*/
_startMemory: Task.async(function *(options) {
if (!options.withAllocations) {
return 0;
}
let memoryStartTime = yield this._startRecordingAllocations(options);
yield this._pullAllocationSites();
return memoryStartTime;
}),
/**
* Stops polling for allocations from the memory actor, if necessary.
*/
_stopMemory: Task.async(function *(options) {
if (!options.withAllocations) {
return 0;
}
// Since `_pullAllocationSites` is usually running inside a timeout, and
// it's performing asynchronous requests to the server, a recording may
// be stopped before that method finishes executing. Therefore, we need to
// wait for the last request to `getAllocations` to finish before actually
// stopping recording allocations.
yield this._lastPullAllocationSitesFinished;
clearTimeout(this._sitesPullTimeout);
return yield this._stopRecordingAllocations();
}),
/**
* Starts recording allocations in the memory actor.
*/
_startRecordingAllocations: Task.async(function*(options) {
yield this._request("memory", "attach");
let memoryStartTime = yield this._request("memory", "startRecordingAllocations", {
probability: options.allocationsSampleProbability,
maxLogLength: options.allocationsMaxLogLength
});
return memoryStartTime;
}),
/**
* Stops recording allocations in the memory actor.
*/
_stopRecordingAllocations: Task.async(function*() {
let memoryEndTime = yield this._request("memory", "stopRecordingAllocations");
yield this._request("memory", "detach");
return memoryEndTime;
}),
/**
* At regular intervals, pull allocations from the memory actor, and forward
* them to consumers.
*/
_pullAllocationSites: Task.async(function *() {
let deferred = promise.defer();
this._lastPullAllocationSitesFinished = deferred.promise;
let isDetached = (yield this._request("memory", "getState")) !== "attached";
if (isDetached) {
deferred.resolve();
return;
}
let memoryData = yield this._request("memory", "getAllocations");
this._onTimelineData("allocations", {
sites: memoryData.allocations,
timestamps: memoryData.allocationsTimestamps,
frames: memoryData.frames,
counts: memoryData.counts
});
let delay = DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT;
this._sitesPullTimeout = setTimeout(this._pullAllocationSites, delay);
deferred.resolve();
}),
_pipeToConnection: function (eventName, ...args) {
this.emit(eventName, ...args);
},
toString: () => "[object PerformanceActorsConnection]"
};
@ -620,7 +415,6 @@ function PerformanceFront(connection) {
EventEmitter.decorate(this);
this._connection = connection;
this._request = connection._request;
// Set when mocks are being used
this._memorySupported = connection._memorySupported;
@ -680,6 +474,17 @@ PerformanceFront.prototype = {
*/
isRecording: function () {
return this._connection.isRecording();
},
/**
* Interacts with the connection's actors. Should only be used in tests.
*/
_request: function (actorName, method, ...args) {
if (!gDevTools.testing) {
throw new Error("PerformanceFront._request may only be used in tests.");
}
let actor = this._connection[`_${actorName}`];
return actor[method].apply(actor, args);
}
};

View File

@ -4,6 +4,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXTRA_JS_MODULES.devtools.performance += [
'modules/actors.js',
'modules/compatibility.js',
'modules/front.js',
'modules/graphs.js',

View File

@ -18,7 +18,7 @@ function spawnTest () {
ok(sharedConnection,
"A shared profiler connection for the current toolbox was retrieved.");
is(sharedConnection._request, panel.panelWin.gFront._request,
is(panel.panelWin.gFront._connection, sharedConnection,
"The same shared profiler connection is used by the panel's front.");
yield sharedConnection.open();

View File

@ -124,7 +124,9 @@ endef
$(foreach subtier,$(filter-out compile,$(TIERS)),$(eval $(call CREATE_SUBTIER_TRAVERSAL_RULE,$(subtier))))
ifndef TOPLEVEL_BUILD
ifdef COMPILE_ENVIRONMENT
libs:: target host
endif # COMPILE_ENVIRONMENT
endif
endif # ifeq ($(NO_RECURSE_MAKELEVEL),$(MAKELEVEL))

View File

@ -367,6 +367,15 @@ dnl ========================================================
dnl AR_FLAGS set here so HOST_AR_FLAGS can be set correctly (see bug 538269)
AR_FLAGS='crs $@'
if test -z "$COMPILE_ENVIRONMENT"; then
if test "$target" != "$host"; then
# Assert that we're cross compiling, but don't require a compile toolchain (as
# MOZ_CROSS_COMPILER does below).
CROSS_COMPILE=1
AC_DEFINE(CROSS_COMPILE)
fi
fi # !COMPILE_ENVIRONMENT
if test "$COMPILE_ENVIRONMENT"; then
if test "$target" != "$host"; then
@ -1965,7 +1974,7 @@ case "$target" in
TARGET_COMPILER_ABI="ibmc"
CC_VERSION=`lslpp -Lcq vac.C 2>/dev/null | awk -F: '{ print $3 }'`
CXX_VERSION=`lslpp -Lcq vacpp.cmp.core 2>/dev/null | awk -F: '{ print $3 }'`
fi
fi # COMPILE_ENVIRONMENT
fi
case "${target_os}" in
aix4.1*)
@ -1974,7 +1983,7 @@ case "$target" in
esac
if test "$COMPILE_ENVIRONMENT"; then
MOZ_CHECK_HEADERS(sys/inttypes.h)
fi
fi # COMPILE_ENVIRONMENT
AC_DEFINE(NSCAP_DISABLE_DEBUG_PTR_TYPES)
;;
@ -2101,7 +2110,9 @@ ia64*-hpux*)
MOZ_SYNTH_PICO=1
else
_PLATFORM_DEFAULT_TOOLKIT=cairo-android
MOZ_LINKER=1
if test "$COMPILE_ENVIRONMENT"; then
MOZ_LINKER=1
fi
fi
TARGET_NSPR_MDCPUCFG='\"md/_linux.cfg\"'
@ -2587,7 +2598,7 @@ if test -z "$COMPILE_ENVIRONMENT"; then
SKIP_LIBRARY_CHECKS=1
else
MOZ_COMPILER_OPTS
fi
fi # COMPILE_ENVIRONMENT
if test -z "$SKIP_COMPILER_CHECKS"; then
dnl Checks for typedefs, structures, and compiler characteristics.
@ -3919,7 +3930,9 @@ case "${target}" in
NSS_DISABLE_DBM=1
MOZ_THEME_FASTSTRIPE=1
MOZ_TREE_FREETYPE=1
MOZ_MEMORY=1
if test "$COMPILE_ENVIRONMENT"; then
MOZ_MEMORY=1
fi
MOZ_RAW=1
;;
@ -5451,7 +5464,7 @@ if test -n "$MOZ_VPX" -a -z "$MOZ_NATIVE_LIBVPX"; then
VPX_ASFLAGS="-f win32 -rnasm -pnasm -DPIC"
VPX_X86_ASM=1
dnl The encoder needs obj_int_extract to get asm offsets.
fi
fi # COMPILE_ENVIRONMENT and others
;;
*:arm*)
if test -n "$GNU_AS" ; then
@ -5481,7 +5494,7 @@ if test -n "$MOZ_VPX" -a -z "$MOZ_NATIVE_LIBVPX"; then
if test -n "$COMPILE_ENVIRONMENT" -a -n "$VPX_X86_ASM" -a -z "$VPX_AS"; then
AC_MSG_ERROR([yasm is a required build tool for this architecture when webm is enabled. You may either install yasm or --disable-webm (which disables the WebM video format). See https://developer.mozilla.org/en/YASM for more details.])
fi
fi # COMPILE_ENVIRONMENT and others
if test -z "$GNU_CC" -a -z "$INTEL_CC" -a -z "$CLANG_CC" ; then
dnl We prefer to get asm offsets using inline assembler, which the above
@ -7339,7 +7352,7 @@ if test -n "$COMPILE_ENVIRONMENT" -a -n "$USE_ELF_HACK"; then
USE_ELF_HACK=
fi
fi
fi
fi # COMPILE_ENVIRONMENT and others.
dnl ========================================================
dnl = libstdc++ compatibility hacks

View File

@ -1,19 +0,0 @@
Adjust SDK integration
======================
The *Adjust install tracking SDK* is a pure-Java library that is conditionally
compiled into Fennec. It's not trivial to integrate such conditional feature
libraries into Fennec without pre-processing. To minimize such pre-processing,
we define a trivial ``AdjustHelperInterface`` and define two implementations:
the real ``AdjustHelper``, which requires the Adjust SDK, and a no-op
``StubAdjustHelper``, which has no additional requirements. We use the existing
pre-processed ``AppConstants.java.in`` to switch, at build-time, between the two
implementations.
An alternative approach would be to build three jars -- one interface jar and
two implementation jars -- and include one of the implementation jars at
build-time. The implementation jars could either define a common symbol, or the
appropriate symbol could be determined at build-time. That's a rather heavier
approach than the one chosen. If the helper class were to grow to multiple
classes, with a non-trivial exposed API, this approach could be better. It
would also be easier to integrate into the parallel Gradle build system.

View File

@ -30,7 +30,13 @@ public class ReferrerReceiver extends BroadcastReceiver {
public static final String ACTION_REFERRER_RECEIVED = "org.mozilla.fennec.REFERRER_RECEIVED";
/**
* If the install intent has this source, we'll track the campaign ID.
* If the install intent has this source, it is a Mozilla specific or over
* the air distribution referral. We'll track the campaign ID using
* Mozilla's metrics systems.
*
* If the install intent has a source different than this one, it is a
* referral from an advertising network. We may track these campaigns using
* third-party tracking and metrics systems.
*/
private static final String MOZILLA_UTM_SOURCE = "mozilla";

View File

@ -0,0 +1,161 @@
.. -*- Mode: rst; fill-column: 100; -*-
======================================
Install tracking with the Adjust SDK
======================================
Fennec (Firefox for Android) tracks certain types of installs using a third party install tracking
framework called Adjust. The intention is to determine the origin of Fennec installs by answering
the question, "Did this user on this device install Fennec in response to a specific advertising
campaign performed by Mozilla?"
Mozilla is using a third party framework in order to answer this question for the Firefox for
Android 38.0.5 release. We hope to remove the framework from Fennec in the future.
The framework consists of a software development kit (SDK) built into Fennec and a
data-collecting Internet service backend run by the German company `adjust GmbH`_. The Adjust SDK
is open source and MIT licensed: see the `github repository`_. Fennec ships a copy of the SDK
(currently not modified from upstream) in ``mobile/android/thirdparty/com/adjust/sdk``. The SDK is
documented at https://docs.adjust.com.
Data collection
~~~~~~~~~~~~~~~
When is data collected and sent to the Adjust backend?
======================================================
Data is never collected (or sent to the Adjust backend) unless
* the Fennec binary is an official Mozilla binary [#official]_; and
* the release channel is Release or Beta [#channel]_.
If both of the above conditions are true, then data is collected and sent to the Adjust backend in
the following two circumstances: first, when
* Fennec is started on the device [#started]_.
Second, when
* the Fennec binary was installed from the Google Play Store; and
* the Google Play Store sends the installed Fennec binary an `INSTALL_REFERRER Intent`_, and the
received Intent includes Google Play Store campaign tracking information. This happens when thea
Google Play Store install is in response to a campaign-specific Google Play Store link. For
details, see the developer documentation at
https://developers.google.com/analytics/devguides/collection/android/v4/campaigns.
In these two limited circumstances, data is collected and sent to the Adjust backend.
Where does data sent to the Adjust backend go?
==============================================
The Adjust SDK is hard-coded to send data to the endpoint https://app.adjust.com. The endpoint is
defined by ``com.adjust.sdk.Constants.BASE_URL`` at
https://hg.mozilla.org/mozilla-central/file/f76f02793f7a/mobile/android/thirdparty/com/adjust/sdk/Constants.java#l27.
The Adjust backend then sends a limited subset of the collected data -- limited but sufficient to
uniquely identify the submitting device -- to a set of advertising network providers that Mozilla
elects to share the collected data with. Those advertising networks then confirm or deny that the
identifying information corresponds to a specific advertising campaign performed by Mozilla.
What data is collected and sent to the Adjust backend?
======================================================
The Adjust SDK collects and sends two messages to the Adjust backend. The messages have the
following parameters::
V/Adjust ( 6508): Parameters:
V/Adjust ( 6508): screen_format normal
V/Adjust ( 6508): device_manufacturer samsung
V/Adjust ( 6508): session_count 1
V/Adjust ( 6508): device_type phone
V/Adjust ( 6508): screen_size normal
V/Adjust ( 6508): package_name org.mozilla.firefox
V/Adjust ( 6508): app_version 39.0a1
V/Adjust ( 6508): android_uuid <guid>
V/Adjust ( 6508): display_width 720
V/Adjust ( 6508): country GB
V/Adjust ( 6508): os_version 18
V/Adjust ( 6508): needs_attribution_data 0
V/Adjust ( 6508): environment sandbox
V/Adjust ( 6508): device_name Galaxy Nexus
V/Adjust ( 6508): os_name android
V/Adjust ( 6508): tracking_enabled 1
V/Adjust ( 6508): created_at 2015-03-24T17:53:38.452Z-0400
V/Adjust ( 6508): app_token <private>
V/Adjust ( 6508): screen_density high
V/Adjust ( 6508): language en
V/Adjust ( 6508): display_height 1184
V/Adjust ( 6508): gps_adid <guid>
V/Adjust ( 6508): Parameters:
V/Adjust ( 6508): needs_attribution_data 0
V/Adjust ( 6508): app_token <private>
V/Adjust ( 6508): environment production
V/Adjust ( 6508): android_uuid <guid>
V/Adjust ( 6508): tracking_enabled 1
V/Adjust ( 6508): gps_adid <guid>
The available parameters (including ones not exposed to Mozilla) are documented at
https://partners.adjust.com/placeholders/.
Notes on what data is collected
-------------------------------
The *android_uuid* uniquely identifies the device.
The *gps_adid* is a Google Advertising ID. It is capable of uniquely identifying a device to any
advertiser, across all applications. If a Google Advertising ID is not available, Adjust may fall
back to an Android ID, or, as a last resort, the device's WiFi MAC address.
The *tracking_enabled* flag is only used to allow or disallow contextual advertising to be sent to a
user. It can be, and is, ignored for general install tracking of the type Mozilla is using the
Adjust SDK for. (This flag might be used by consumers using the Adjust SDK to provide in-App
advertising.)
It is not clear how much entropy their is in the set of per-device parameters that do not
*explicitly* uniquely identify the device. That is, it is not known if the device parameters are
likely to uniquely fingerprint the device, in the way that user agent capabilities are likely to
uniquely fingerprint the user.
Technical notes
~~~~~~~~~~~~~~~
Build flags controlling the Adjust SDK integration
==================================================
The Adjust SDK feature is controlled by the build flag ``MOZ_INSTALL_TRACKING``. No trace of the
Adjust SDK should be present in Fennec if this is not defined.
Access to the Adjust backend is controlled by a private App-specific token. Fennec's token is
managed by Release Engineering and should not be exposed if at all possible; for example, it should
*not* leak to build logs. The value of the token is read from the file specified using the
``configure`` flag ``--with-adjust-sdk-keyfile=KEYFILE`` and stored in the build variable
``MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN``. Nota bene: if ``MOZ_INSTALL_TRACKING`` is defined
but the App-specific token is not specified, Fennec will submit data to a special Adjust sandbox.
This makes it possible to test the Adjust flow without submitting false data to the install tracking
backend.
Technical notes on the Adjust SDK integration
=============================================
The *Adjust install tracking SDK* is a pure-Java library that is conditionally compiled into Fennec.
It's not trivial to integrate such conditional feature libraries into Fennec without pre-processing.
To minimize such pre-processing, we define a trivial ``AdjustHelperInterface`` and define two
implementations: the real ``AdjustHelper``, which requires the Adjust SDK, and a no-op
``StubAdjustHelper``, which has no additional requirements. We use the existing pre-processed
``AppConstants.java.in`` to switch, at build-time, between the two implementations.
Notes and links
===============
.. _adjust GmbH: http://www.adjust.com
.. _github repository: https://github.com/adjust/android_sdk
.. [#official] Data is not sent for builds not produced by Mozilla: this would include
redistributors such as the Palemoon project.
.. [#channel] Data is not sent for Aurora, Nightly, or custom builds.
.. [#started] *Started* means more than just when the user taps the Fennec icon or otherwise causes
the Fennec user interface to appear directly. It includes, for example, when a Fennec service
(like the Update Service, or Background Sync), starts and Fennec was not previously running on the
device. See http://developer.android.com/reference/android/app/Application.html#onCreate%28%29
for details.
.. _INSTALL_REFERRER Intent: https://developer.android.com/reference/com/google/android/gms/tagmanager/InstallReferrerReceiver.html

View File

@ -7,4 +7,5 @@ Firefox for Android
localeswitching
uitelemetry
adjust
gradle

View File

@ -760,6 +760,10 @@ for var in ('ANDROID_PACKAGE_NAME', 'ANDROID_CPU_ARCH',
# Mangle our package name to avoid Bug 750548.
DEFINES['MANGLED_ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME'].replace('fennec', 'f3nn3c')
DEFINES['MOZ_APP_ABI'] = CONFIG['TARGET_XPCOM_ABI']
if not CONFIG['COMPILE_ENVIRONMENT']:
# These should really come from the included binaries, but that's not easy.
DEFINES['MOZ_APP_ABI'] = 'arm-eabi-gcc3' # Observe quote differences here ...
DEFINES['TARGET_XPCOM_ABI'] = '"arm-eabi-gcc3"' # ... and here.
if '-march=armv7' in CONFIG['OS_CFLAGS']:
DEFINES['MOZ_MIN_CPU_VERSION'] = 7

View File

@ -6,7 +6,7 @@
<resources>
<dimen name="arrow_popup_container_width">400dp</dimen>
<dimen name="doorhanger_offsetY">124dp</dimen>
<dimen name="doorhanger_offsetY">100dp</dimen>
<dimen name="browser_toolbar_height">56dp</dimen>
<dimen name="browser_toolbar_height_flipper">60dp</dimen>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<dimen name="doorhanger_offsetY">124dp</dimen>
</resources>

View File

@ -46,8 +46,11 @@ MOZ_APP_ID={aa3c5121-dab2-40e2-81ca-7ea25febc110}
MOZ_APP_STATIC_INI=1
# Enable on-demand decompression
# Enable on-demand decompression. This requires a host compile toolchain to
# build szip to use during packaging.
if test "$COMPILE_ENVIRONMENT"; then
MOZ_ENABLE_SZIP=1
fi
# Enable navigator.mozPay
MOZ_PAY=1

View File

@ -1122,9 +1122,9 @@ class AndroidCommands(MachCommandBase):
'--robocop-ini=' +
os.path.join(
self.topobjdir,
'build',
'mobile',
'robocop',
'_tests',
'testing',
'mochitest',
'robocop.ini'),
'--log-mach=-',
]

View File

@ -66,7 +66,7 @@ RUN_MOCHITEST_ROBOCOP = \
$(PYTHON) _tests/testing/mochitest/runtestsremote.py \
--robocop-apk=$(DEPTH)/build/mobile/robocop/robocop-debug.apk \
--robocop-ids=$(DEPTH)/mobile/android/base/fennec_ids.txt \
--robocop-ini=$(DEPTH)/build/mobile/robocop/robocop.ini \
--robocop-ini=_tests/testing/mochitest/robocop.ini \
--console-level=INFO --log-tbpl=./$@.log $(DM_FLAGS) --dm_trans=$(DM_TRANS) \
--app=$(TEST_PACKAGE_NAME) --deviceIP=${TEST_DEVICE} --xre-path=${MOZ_HOST_BIN} \
$(SYMBOLS_PATH) $(TEST_PATH_ARG) $(EXTRA_TEST_ARGS)

View File

@ -3,6 +3,7 @@
; file, You can obtain one at http://mozilla.org/MPL/2.0/.
[DEFAULT]
tags = appupdate
skip-if = (buildapp == 'b2g' || buildapp == 'mulet')
support-files =
utils.js

View File

@ -3,6 +3,7 @@
; file, You can obtain one at http://mozilla.org/MPL/2.0/.
[DEFAULT]
tags = appupdate
head = head_update.js
tail =

View File

@ -7,6 +7,7 @@
; from running the tests in the moz.build file.
[DEFAULT]
tags = appupdate
head = head_update.js
tail =

View File

@ -5,6 +5,7 @@
; Tests that require the updater binary and the maintenance service.
[DEFAULT]
tags = appupdate
head = head_update.js
tail =