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