1 /* See license.txt for terms of usage */
  2 
  3 // ********************************************************************************************* //
  4 // Constants
  5 
  6 const Cc = Components.classes;
  7 const Ci = Components.interfaces;
  8 const Cr = Components.results;
  9 const Cu = Components.utils;
 10 
 11 var EXPORTED_SYMBOLS = ["traceConsoleService"];
 12 
 13 const PrefService = Cc["@mozilla.org/preferences-service;1"];
 14 const prefs = PrefService.getService(Ci.nsIPrefBranch);
 15 const prefService = PrefService.getService(Ci.nsIPrefService);
 16 
 17 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 18 Cu.import("resource://gre/modules/Services.jsm");
 19 Cu.import("resource://gre/modules/AddonManager.jsm");
 20 
 21 // xxxHonza: could we remove some of them?
 22 var TraceAPI = ["dump", "sysout", "setScope", "matchesNode", "time", "timeEnd"];
 23 
 24 // ********************************************************************************************* //
 25 // Service Implementation
 26 
 27 try
 28 {
 29     Cu["import"]("resource://fbtrace/firebug-trace-service.js");
 30 }
 31 catch (err)
 32 {
 33     // Tracing Console is not available yet, let's use a fake one.
 34     var traceConsoleService =
 35     {
 36         tracers: {},
 37 
 38         getTracer: function(prefDomain)
 39         {
 40             var tracer = this.tracers[prefDomain];
 41             if (tracer)
 42                 return tracer;
 43 
 44             var enabledAddons = decodeURIComponent(getCharPref("extensions", "enabledAddons"));
 45             if (enabledAddons.indexOf("fbtrace@getfirebug.com:") >= 0)
 46             {
 47                 // Solution with built-in buffer for logs created before console is ready.
 48                 var wrapper = new TracerWrapper(prefDomain);
 49                 tracer = wrapper.createTracer();
 50             }
 51             else
 52             {
 53                 // Simple empty implementation for cases where Firebug Tracing Console
 54                 // is not even installed or 'alwaysOpenTraceConsole' is set to false.
 55                 tracer = {};
 56                 for (var i=0; i<TraceAPI.length; i++)
 57                     tracer[TraceAPI[i]] = function() {};
 58             }
 59 
 60             this.tracers[prefDomain] = tracer;
 61             return tracer;
 62         },
 63     }
 64 }
 65 
 66 // ********************************************************************************************* //
 67 // Tracer Wrapper
 68 
 69 /**
 70  * Trace Wrapper represents a temporary Trace object that is used till the real Tracing
 71  * Console is opened and available to use. Trace Wrapper implements a buffer that
 72  * collects all logs and flushes them as intot the real console as soon as it's ready.
 73  *
 74  * In order to use this functionality, you need to set:
 75  *    'extensions.firebug.alwaysOpenTraceConsole' to true
 76  *
 77  * @param {Object} prefDomain Associated pref domain. Usually 'extensions.firebug'
 78  */
 79 function TracerWrapper(prefDomain)
 80 {
 81     this.prefDomain = prefDomain;
 82 }
 83 
 84 TracerWrapper.prototype =
 85 {
 86     // Temporary trace object
 87     tracer: null,
 88 
 89     // Buffer with logs used till the Tracing Console UI is available
 90     queue: [],
 91 
 92     // The real tracing console tracer object.
 93     FBTrace: null,
 94 
 95     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 96     // Tracer
 97 
 98     createTracer: function()
 99     {
100         var self = this;
101 
102         this.addObserver();
103 
104         // Default FBTrace implementation puts all calls in a buffer.
105         // It'll be used as soon as the console is ready.
106         function createHandler(method)
107         {
108             return function() {
109                 self.push(method, arguments);
110             }
111         };
112 
113         // Fake FBTrace object
114         this.tracer = {};
115 
116         // Dynamically create APIs.
117         for (var i=0; i<TraceAPI.length; i++)
118         {
119             var method = TraceAPI[i];
120             this.tracer[method] = createHandler(method);
121         }
122 
123         var branch = prefService.getBranch(this.prefDomain);
124         var arrayDesc = {};
125 
126         // Set options from preferences.
127         var children = branch.getChildList("", arrayDesc);
128         for (var i=0; i<children.length; i++)
129         {
130             var name = children[i];
131             var m = name.indexOf("DBG_");
132             if (m != -1)
133             {
134                 var optionName = name.substr(1); // drop leading .
135                 this.tracer[optionName] = getBoolPref(this.prefDomain, optionName);
136             }
137         }
138 
139         // Create FBTrace proxy. As soon as FBTrace console is available it'll forward
140         // all calls to it.
141         return Proxy.create(
142         {
143             get: function(target, name)
144             {
145                 return self.FBTrace ? self.FBTrace[name] : self.tracer[name];
146             },
147 
148             set: function(target, name, value)
149             {
150                 if (self.FBTrace)
151                     self.FBTrace[name] = value;
152                 else
153                     self.tracer[name] = value;
154 
155                 return true;
156             },
157         });
158     },
159 
160     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
161     // Buffer
162 
163     push: function(method, args)
164     {
165         if (!this.queue)
166             return;
167 
168         this.queue.push({
169             method: method,
170             args: args,
171         });
172 
173         // Size of the buffer is limited.
174         while (this.queue.length > 1000)
175             this.queue.pop();
176     },
177 
178     clearBuffer: function()
179     {
180         this.queue = null;
181     },
182 
183     flush: function()
184     {
185         if (!this.FBTrace || !this.queue)
186             return;
187 
188         if (this.queue.length > 0)
189             this.FBTrace.sysout("FBTrace: flush " + this.queue.length + " buffered logs:");
190 
191         for (var i=0; i<this.queue.length; i++)
192         {
193             var call = this.queue[i];
194             this.FBTrace[call.method].apply(this.FBTrace, call.args);
195         }
196 
197         this.clearBuffer();
198     },
199 
200     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
201     // FBTrace Console Observer
202 
203     addObserver: function()
204     {
205         // Listen for new windows, Firebug must be loaded into them too.
206         Services.obs.addObserver(this, "chrome-document-global-created", false);
207     },
208 
209     QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
210     observe: function windowWatcher(win, topic, data)
211     {
212         // xxxHonza: the window should be associated with the same prefDomain.
213         if (win.location.href == "chrome://fbtrace/content/traceLogFrame.html")
214         {
215             var self = this;
216 
217             // https://bugzil.la/795961 ?
218             win.addEventListener("load", function onLoad(evt)
219             { 
220                 // load listener not necessary once https://bugzil.la/800677 is fixed
221                 var win = evt.currentTarget;
222                 win.removeEventListener("load", onLoad, false);
223 
224                 self.initFBTrace(self.prefDomain);
225             }, false);
226         }
227     },
228 
229     initFBTrace: function()
230     {
231         if (this.FBTrace)
232             return this.FBTrace;
233 
234         try
235         {
236             var scope = {};
237             Cu.import("resource://fbtrace/firebug-trace-service.js", scope);
238             this.FBTrace = scope.traceConsoleService.getTracer(this.prefDomain);
239 
240             // FBTrace Console is ready let's flush the log buffer.
241             this.flush();
242         }
243         catch (err)
244         {
245         }
246     }
247 }
248 
249 // ********************************************************************************************* //
250 // Helpers
251 
252 function getStackDump()
253 {
254     var lines = [];
255     for (var frame = Components.stack; frame; frame = frame.caller)
256         lines.push(frame.filename + " (" + frame.lineNumber + ")");
257 
258     return lines.join("\n");
259 };
260 
261 function getBoolPref(prefDomain, name)
262 {
263     try
264     {
265         var prefName = prefDomain + "." + name;
266         return prefs.getBoolPref(prefName);
267     }
268     catch (err)
269     {
270     }
271 
272     return false;
273 }
274 
275 function getCharPref(prefDomain, name)
276 {
277     try
278     {
279         var prefName = prefDomain + "." + name;
280         return prefs.getCharPref(prefName);
281     }
282     catch (err)
283     {
284     }
285 
286     return false;
287 }
288 
289 // ********************************************************************************************* //
290