Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 var isJSM = typeof require !== "function";
9 // This code is needed because, for whatever reason, mochitest can't
10 // find any requirejs module so we have to load it old school way. :(
12 if (isJSM) {
13 var Cu = this["Components"].utils;
14 let XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}).XPCOMUtils;
15 this["loader"] = { lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils) };
16 this["require"] = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
17 } else {
18 var { Cu } = require("chrome");
19 }
21 const { L10N_BUNDLE } = require("devtools/profiler/consts");
23 var EventEmitter = require("devtools/toolkit/event-emitter");
25 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
26 Cu.import("resource://gre/modules/devtools/Console.jsm");
27 Cu.import("resource://gre/modules/AddonManager.jsm");
28 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
30 loader.lazyGetter(this, "L10N", () => new ViewHelpers.L10N(L10N_BUNDLE));
32 loader.lazyGetter(this, "gDevTools",
33 () => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools);
35 loader.lazyGetter(this, "DebuggerServer",
36 () => Cu.import("resource:///modules/devtools/dbg-server.jsm", {}).DebuggerServer);
38 /**
39 * Data structure that contains information that has
40 * to be shared between separate ProfilerController
41 * instances.
42 */
43 const sharedData = {
44 data: new WeakMap(),
45 controllers: new WeakMap(),
46 };
48 /**
49 * Makes a structure representing an individual profile.
50 */
51 function makeProfile(name, def={}) {
52 if (def.timeStarted == null)
53 def.timeStarted = null;
55 if (def.timeEnded == null)
56 def.timeEnded = null;
58 return {
59 name: name,
60 timeStarted: def.timeStarted,
61 timeEnded: def.timeEnded,
62 fromConsole: def.fromConsole || false
63 };
64 }
66 // Three functions below all operate with sharedData
67 // structure defined above. They should be self-explanatory.
69 function addTarget(target) {
70 sharedData.data.set(target, new Map());
71 }
73 function getProfiles(target) {
74 return sharedData.data.get(target);
75 }
77 /**
78 * Object to control the JavaScript Profiler over the remote
79 * debugging protocol.
80 *
81 * @param Target target
82 * A target object as defined in Target.jsm
83 */
84 function ProfilerController(target) {
85 if (sharedData.controllers.has(target)) {
86 return sharedData.controllers.get(target);
87 }
89 this.target = target;
90 this.client = target.client;
91 this.isConnected = false;
92 this.consoleProfiles = [];
93 this.reservedNames = {};
95 addTarget(target);
97 // Chrome debugging targets have already obtained a reference
98 // to the profiler actor.
99 if (target.chrome) {
100 this.isConnected = true;
101 this.actor = target.form.profilerActor;
102 }
104 sharedData.controllers.set(target, this);
105 EventEmitter.decorate(this);
106 };
108 ProfilerController.prototype = {
109 target: null,
110 client: null,
111 isConnected: null,
112 consoleProfiles: null,
113 reservedNames: null,
115 /**
116 * Return a map of profile results for the current target.
117 *
118 * @return Map
119 */
120 get profiles() {
121 return getProfiles(this.target);
122 },
124 /**
125 * Checks whether the profile is currently recording.
126 *
127 * @param object profile
128 * An object made by calling makeProfile function.
129 * @return boolean
130 */
131 isProfileRecording: function PC_isProfileRecording(profile) {
132 return profile.timeStarted !== null && profile.timeEnded === null;
133 },
135 getProfileName: function PC_getProfileName() {
136 let num = 1;
137 let name = L10N.getFormatStr("profiler.profileName", [num]);
139 while (this.reservedNames[name]) {
140 num += 1;
141 name = L10N.getFormatStr("profiler.profileName", [num]);
142 }
144 this.reservedNames[name] = true;
145 return name;
146 },
148 /**
149 * A listener that fires whenever console.profile or console.profileEnd
150 * is called.
151 *
152 * @param string type
153 * Type of a call. Either 'profile' or 'profileEnd'.
154 * @param object data
155 * Event data.
156 */
157 onConsoleEvent: function (type, data) {
158 let name = data.extra.name;
160 let profileStart = () => {
161 if (name && this.profiles.has(name))
162 return;
164 // Add profile structure to shared data.
165 let profile = makeProfile(name || this.getProfileName(), {
166 timeStarted: data.extra.currentTime,
167 fromConsole: true
168 });
170 this.profiles.set(profile.name, profile);
171 this.consoleProfiles.push(profile.name);
172 this.emit("profileStart", profile);
173 };
175 let profileEnd = () => {
176 if (!name && !this.consoleProfiles.length)
177 return;
179 if (!name)
180 name = this.consoleProfiles.pop();
181 else
182 this.consoleProfiles.filter((n) => n !== name);
184 if (!this.profiles.has(name))
185 return;
187 let profile = this.profiles.get(name);
188 if (!this.isProfileRecording(profile))
189 return;
191 let profileData = data.extra.profile;
192 profileData.threads = profileData.threads.map((thread) => {
193 let samples = thread.samples.filter((sample) => {
194 return sample.time >= profile.timeStarted;
195 });
197 return { samples: samples };
198 });
200 profile.timeEnded = data.extra.currentTime;
201 profile.data = profileData;
203 this.emit("profileEnd", profile);
204 };
206 if (type === "profile")
207 profileStart();
209 if (type === "profileEnd")
210 profileEnd();
211 },
213 /**
214 * Connects to the client unless we're already connected.
215 *
216 * @param function cb
217 * Function to be called once we're connected. If
218 * the controller is already connected, this function
219 * will be called immediately (synchronously).
220 */
221 connect: function (cb=function(){}) {
222 if (this.isConnected) {
223 return void cb();
224 }
226 // Check if we already have a grip to the listTabs response object
227 // and, if we do, use it to get to the profilerActor. Otherwise,
228 // call listTabs. The problem is that if we call listTabs twice
229 // webconsole tests fail (see bug 872826).
231 let register = () => {
232 let data = { events: ["console-api-profiler"] };
234 // Check if Gecko Profiler Addon [1] is installed and, if it is,
235 // don't register our own console event listeners. Gecko Profiler
236 // Addon takes care of console.profile and console.profileEnd methods
237 // and we don't want to break it.
238 //
239 // [1] - https://github.com/bgirard/Gecko-Profiler-Addon/
241 AddonManager.getAddonByID("jid0-edalmuivkozlouyij0lpdx548bc@jetpack", (addon) => {
242 if (addon && !addon.userDisabled && !addon.softDisabled)
243 return void cb();
245 this.request("registerEventNotifications", data, (resp) => {
246 this.client.addListener("eventNotification", (type, resp) => {
247 let toolbox = gDevTools.getToolbox(this.target);
248 if (toolbox == null)
249 return;
251 this.onConsoleEvent(resp.subject.action, resp.data);
252 });
253 });
255 cb();
256 });
257 };
259 if (this.target.root) {
260 this.actor = this.target.root.profilerActor;
261 this.isConnected = true;
262 return void register();
263 }
265 this.client.listTabs((resp) => {
266 this.actor = resp.profilerActor;
267 this.isConnected = true;
268 register();
269 });
270 },
272 /**
273 * Adds actor and type information to data and sends the request over
274 * the remote debugging protocol.
275 *
276 * @param string type
277 * Method to call on the other side
278 * @param object data
279 * Data to send with the request
280 * @param function cb
281 * A callback function
282 */
283 request: function (type, data, cb) {
284 data.to = this.actor;
285 data.type = type;
286 this.client.request(data, cb);
287 },
289 /**
290 * Checks whether the profiler is active.
291 *
292 * @param function cb
293 * Function to be called with a response from the
294 * client. It will be called with two arguments:
295 * an error object (may be null) and a boolean
296 * value indicating if the profiler is active or not.
297 */
298 isActive: function (cb) {
299 this.request("isActive", {}, (resp) => {
300 cb(resp.error, resp.isActive, resp.currentTime);
301 });
302 },
304 /**
305 * Creates a new profile and starts the profiler, if needed.
306 *
307 * @param string name
308 * Name of the profile.
309 * @param function cb
310 * Function to be called once the profiler is started
311 * or we get an error. It will be called with a single
312 * argument: an error object (may be null).
313 */
314 start: function PC_start(name, cb) {
315 if (this.profiles.has(name)) {
316 return;
317 }
319 let profile = makeProfile(name);
320 this.consoleProfiles.push(name);
321 this.profiles.set(name, profile);
323 // If profile is already running, no need to do anything.
324 if (this.isProfileRecording(profile)) {
325 return void cb();
326 }
328 this.isActive((err, isActive, currentTime) => {
329 if (isActive) {
330 profile.timeStarted = currentTime;
331 return void cb();
332 }
334 let params = {
335 entries: 1000000,
336 interval: 1,
337 features: ["js"],
338 };
340 this.request("startProfiler", params, (resp) => {
341 if (resp.error) {
342 return void cb(resp.error);
343 }
345 profile.timeStarted = 0;
346 cb();
347 });
348 });
349 },
351 /**
352 * Stops the profiler. NOTE, that we don't stop the actual
353 * SPS Profiler here. It will be stopped as soon as all
354 * clients disconnect from the profiler actor.
355 *
356 * @param string name
357 * Name of the profile that needs to be stopped.
358 * @param function cb
359 * Function to be called once the profiler is stopped
360 * or we get an error. It will be called with a single
361 * argument: an error object (may be null).
362 */
363 stop: function PC_stop(name, cb) {
364 if (!this.profiles.has(name)) {
365 return;
366 }
368 let profile = this.profiles.get(name);
369 if (!this.isProfileRecording(profile)) {
370 return;
371 }
373 this.request("getProfile", {}, (resp) => {
374 if (resp.error) {
375 Cu.reportError("Failed to fetch profile data.");
376 return void cb(resp.error, null);
377 }
379 let data = resp.profile;
380 profile.timeEnded = resp.currentTime;
382 // Filter out all samples that fall out of current
383 // profile's range.
385 data.threads = data.threads.map((thread) => {
386 let samples = thread.samples.filter((sample) => {
387 return sample.time >= profile.timeStarted;
388 });
390 return { samples: samples };
391 });
393 cb(null, data);
394 });
395 },
397 /**
398 * Cleanup.
399 */
400 destroy: function PC_destroy() {
401 this.client = null;
402 this.target = null;
403 this.actor = null;
404 }
405 };
407 if (isJSM) {
408 var EXPORTED_SYMBOLS = ["ProfilerController"];
409 } else {
410 module.exports = ProfilerController;
411 }