1 /* See license.txt for terms of usage */
  2 
  3 // ************************************************************************************************
  4 // Constants
  5 
  6 const CLASS_ID = Components.ID("{D2AC51BC-1622-4d4d-85CB-F8E8B5805CB9}");
  7 const CLASS_NAME = "Firebug Trace Console Service";
  8 const CONTRACT_ID = "@joehewitt.com/firebug-trace-service;1";
  9 const EXTENSIONS = "extensions";
 10 const DBG_ = "DBG_";
 11 
 12 const Cc = Components.classes;
 13 const Ci = Components.interfaces;
 14 const Cr = Components.results;
 15 
 16 const PrefService = Cc["@mozilla.org/preferences-service;1"];
 17 const prefs = PrefService.getService(Ci.nsIPrefBranch2);
 18 const prefService = PrefService.getService(Ci.nsIPrefService);
 19 const consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
 20 
 21 const appShellService = Components.classes["@mozilla.org/appshell/appShellService;1"].getService(Components.interfaces.nsIAppShellService);
 22 
 23 // ************************************************************************************************
 24 // Service implementation
 25 
 26 
 27 var toOSConsole = false;
 28 
 29 TraceConsoleService =
 30 {
 31     initialize: function() {
 32         this.observers = [];
 33         this.optionMaps = {};
 34 
 35         // Listen for preferences changes. Trace Options can be changed at run time.
 36         prefs.addObserver("extensions", this, false);
 37 
 38         this.wrappedJSObject = this;
 39         return this;
 40     },
 41 
 42     osOut: function(str)
 43     {
 44         if (!this.outChannel)
 45         {
 46             try
 47             {
 48                 var appShellService = Components.classes["@mozilla.org/appshell/appShellService;1"].
 49                     getService(Components.interfaces.nsIAppShellService);
 50                 this.hiddenWindow = appShellService.hiddenDOMWindow;
 51                 this.outChannel = "hidden";
 52             }
 53             catch(exc)
 54             {
 55                 var consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
 56                 this.outChannel = "service"
 57                 this.outChannel("Using consoleService because nsIAppShellService.hiddenDOMWindow not available "+exc);
 58             }
 59         }
 60         if (this.outChannel === "hidden")  // apparently can't call via JS function
 61             this.hiddenWindow.dump(str);
 62         else
 63             consoleService.logStringMessage(str);
 64     },
 65 
 66     getTracer: function(prefDomain)
 67     {
 68         if (this.getPref("extensions.firebug-tracing-service.DBG_toOSConsole"))
 69         {
 70              toOSConsole = true;  // also need browser.dom.window.dump.enabled true
 71              TraceConsoleService.osOut("TraceConsoleService.getTracer, prefDomain: "+prefDomain+"\n");
 72         }
 73 
 74         if (!this.optionMaps[prefDomain])
 75             this.optionMaps[prefDomain] = this.createManagedOptionMap(prefDomain);
 76 
 77         return this.optionMaps[prefDomain];
 78     },
 79 
 80     createManagedOptionMap: function(prefDomain)
 81     {
 82         var optionMap = new TraceBase(prefDomain);
 83 
 84         var branch = prefService.getBranch ( prefDomain );
 85         var arrayDesc = {};
 86         var children = branch.getChildList("", arrayDesc);
 87         for (var i = 0; i < children.length; i++)
 88         {
 89             var p = children[i];
 90             var m = p.indexOf("DBG_");
 91             if (m != -1)
 92             {
 93                 var optionName = p.substr(1); // drop leading .
 94                 optionMap[optionName] = this.getPref(prefDomain+p);
 95                 if (toOSConsole)
 96                     this.osOut("TraceConsoleService.createManagedOptionMap "+optionName+"="+optionMap[optionName]+"\n");
 97             }
 98         }
 99 
100         return optionMap;
101     },
102 
103     /* nsIObserve */
104     observe: function(subject, topic, data)
105     {
106         if (data.substr(0,EXTENSIONS.length) == EXTENSIONS)
107         {
108             for (var prefDomain in gTraceService.optionMaps)
109             {
110                 if (data.substr(0, prefDomain.length) == prefDomain)
111                 {
112                     var optionName = data.substr(prefDomain.length+1); // skip dot
113                     if (optionName.substr(0, DBG_.length) == DBG_)
114                         gTraceService.optionMaps[prefDomain][optionName] = this.getPref(data);
115                     if (toOSConsole)
116                         TraceConsoleService.osOut("TraceConsoleService.observe, prefDomain: "+prefDomain+" optionName "+optionName+"\n");
117                 }
118             }
119         }
120     },
121 
122     getPref: function(prefName)
123     {
124         var type = prefs.getPrefType(prefName);
125         if (type == Ci.nsIPrefBranch.PREF_STRING)
126             return prefs.getCharPref(prefName);
127         else if (type == Ci.nsIPrefBranch.PREF_INT)
128             return prefs.getIntPref(prefName);
129         else if (type == Ci.nsIPrefBranch.PREF_BOOL)
130             return prefs.getBoolPref(prefName);
131     },
132 
133     // Prepare trace-object and dispatch to all observers.
134     dispatch: function(messageType, message, obj, scope)
135     {
136         // Translate string object.
137         if (typeof(obj) == "string") {
138             var string = Cc["@mozilla.org/supports-cstring;1"].createInstance(Ci.nsISupportsCString);
139             string.data = obj;
140             obj = string;
141         }
142 
143         // Create wrapper with message type info.
144         var messageInfo = {
145             obj: obj,
146             type: messageType,
147             scope: scope,
148             time: (new Date()).getTime()
149         };
150         if (toOSConsole)
151             TraceConsoleService.osOut(messageType+": "+message+"\n");
152         // Pass JS object properly through XPConnect.
153         var wrappedSubject = {wrappedJSObject: messageInfo};
154         gTraceService.notifyObservers(wrappedSubject, "firebug-trace-on-message", message);
155     },
156 
157     /* nsIObserverService */
158     addObserver: function(observer, topic, weak)
159     {
160         if (topic != "firebug-trace-on-message")
161             throw Cr.NS_ERROR_INVALID_ARG;
162 
163         if (this.observers.length == 0) // mark where trace begins.
164             lastResort(this.observers, topic, "addObserver");
165 
166         this.observers.push(observer);
167     },
168 
169     removeObserver: function(observer, topic)
170     {
171         if (topic != "firebug-trace-on-message")
172             throw Cr.NS_ERROR_INVALID_ARG;
173 
174         for (var i=0; i < this.observers.length; i++) {
175             if (this.observers[i] == observer) {
176                 this.observers.splice(i, 1);
177                 break;
178             }
179         }
180     },
181 
182     notifyObservers: function(subject, topic, someData)
183     {
184         if (this.observers.length > 0)
185         {
186             for (var i=0; i < this.observers.length; i++)
187             {
188                 try
189                 {
190                     this.observers[i].observe(subject, topic, someData);
191                 }
192                 catch (err)
193                 {
194                     // If it's not possible to distribute the log through registered observers,
195                     // use Firefox ErrorConsole. Ultimately the trace-console listens for it
196                     // too and so, will display that.
197                     var scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
198                     scriptError.init("[JavaScript Error: Failed to notify firebug-trace observers!] " +
199                         err.toString(), err.sourceName,
200                         err.sourceLine, err.lineNumber, err.columnNumber, err.flags, err.category);
201                     consoleService.logMessage(scriptError);
202                 }
203             }
204         }
205         else
206         {
207             lastResort(this.observers, subject, someData);
208         }
209     },
210 
211     enumerateObservers: function(topic)
212     {
213         return null;
214     },
215 
216     /* nsISupports */
217     QueryInterface: function(iid)
218     {
219         if (iid.equals(Ci.nsISupports) ||
220             iid.equals(Ci.nsIObserverService))
221              return this;
222 
223         throw Cr.NS_ERROR_NO_INTERFACE;
224     }
225 };
226 
227 function lastResort(listeners, subject, someData)
228 {
229     var unwrapped = subject.wrappedJSObject;
230     if (unwrapped)
231         var objPart = unwrapped.obj ? (" obj: "+unwrapped.obj) : "";
232     else
233         var objPart = subject;
234 
235     TraceConsoleService.osOut("FTS"+listeners.length+": "+someData+" "+objPart+"\n");
236 }
237 // ************************************************************************************************
238 // Public TraceService API
239 
240 // Prevent tracing from code that performs tracing.
241 var noTrace = false;
242 
243 var TraceAPI = {
244     dump: function(messageType, message, obj) {
245         if (noTrace)
246             return;
247 
248         noTrace = true;
249         try
250         {
251             gTraceService.dispatch(messageType, message, obj);
252         }
253         catch(exc)
254         {
255         }
256         finally
257         {
258             noTrace = false;
259         }
260     },
261 
262     sysout: function(message, obj) {
263         this.dump(null, message, obj);
264     },
265 
266     setScope: function(scope)
267     {
268         this.scopeOfFBTrace = scope;
269     },
270 
271     matchesNode: function(node)
272     {
273         return (node.getAttribute('anonid')=="title-box");
274     },
275 
276 };
277 
278 var TraceBase = function(prefDomain) {
279     this.prefDomain = prefDomain;
280 }
281 //Derive all properties from TraceAPI
282 for (var p in TraceAPI)
283     TraceBase.prototype[p] = TraceAPI[p];
284 
285 TraceBase.prototype.sysout = function(message, obj) {
286         if (noTrace)
287             return;
288 
289         noTrace = true;
290 
291         try
292         {
293             gTraceService.dispatch(this.prefDomain, message, obj, this.scopeOfFBTrace);
294         }
295         catch(exc)
296         {
297             if (toOSConsole)
298                 TraceConsoleService.osOut("gTraceService.dispatch FAILS "+exc);
299         }
300         finally
301         {
302             noTrace = false;
303         }
304 }
305 
306 
307 
308 
309 // ************************************************************************************************
310 // Service factory
311 
312 var gTraceService = null;
313 var TraceConsoleServiceFactory =
314 {
315     createInstance: function (outer, iid)
316     {
317         if (outer != null)
318             throw Cr.NS_ERROR_NO_AGGREGATION;
319 
320         if (iid.equals(Ci.nsISupports) ||
321             iid.equals(Ci.nsIObserverService))
322         {
323             if (!gTraceService)
324                 gTraceService = TraceConsoleService.initialize();
325 
326             return gTraceService.QueryInterface(iid);
327         }
328 
329         throw Cr.NS_ERROR_NO_INTERFACE;
330     },
331 
332     QueryInterface: function(iid)
333     {
334         if (iid.equals(Ci.nsISupports) ||
335             iid.equals(Ci.nsISupportsWeakReference) ||
336             iid.equals(Ci.nsIFactory))
337             return this;
338 
339         throw Cr.NS_ERROR_NO_INTERFACE;
340     }
341 };
342 
343 // ************************************************************************************************
344 // Module implementation
345 
346 var TraceConsoleServiceModule =
347 {
348     registerSelf: function (compMgr, fileSpec, location, type)
349     {
350         compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
351         compMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME,
352             CONTRACT_ID, fileSpec, location, type);
353     },
354 
355     unregisterSelf: function(compMgr, fileSpec, location)
356     {
357         compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
358         compMgr.unregisterFactoryLocation(CLASS_ID, location);
359     },
360 
361     getClassObject: function (compMgr, cid, iid)
362     {
363         if (!iid.equals(Ci.nsIFactory))
364             throw Cr.NS_ERROR_NOT_IMPLEMENTED;
365 
366         if (cid.equals(CLASS_ID))
367             return TraceConsoleServiceFactory;
368 
369         throw Cr.NS_ERROR_NO_INTERFACE;
370     },
371 
372     canUnload: function(compMgr)
373     {
374         return true;
375     }
376 };
377 
378 // ************************************************************************************************
379 
380 function NSGetModule(compMgr, fileSpec)
381 {
382     return TraceConsoleServiceModule;
383 }
384