1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/object", 5 "firebug/firebug", 6 "firebug/lib/xpcom", 7 "firebug/lib/url", 8 "firebug/lib/http", 9 "firebug/lib/string" 10 ], 11 function(Obj, Firebug, Xpcom, Url, Http, Str) { 12 13 // ********************************************************************************************* // 14 // Constants 15 16 const Cc = Components.classes; 17 const Ci = Components.interfaces; 18 const nsIIOService = Ci.nsIIOService; 19 const nsIRequest = Ci.nsIRequest; 20 const nsICachingChannel = Ci.nsICachingChannel; 21 const nsIScriptableInputStream = Ci.nsIScriptableInputStream; 22 const nsIUploadChannel = Ci.nsIUploadChannel; 23 const nsIHttpChannel = Ci.nsIHttpChannel; 24 25 const IOService = Cc["@mozilla.org/network/io-service;1"]; 26 const ioService = IOService.getService(nsIIOService); 27 const ScriptableInputStream = Cc["@mozilla.org/scriptableinputstream;1"]; 28 const chromeReg = Xpcom.CCSV("@mozilla.org/chrome/chrome-registry;1", "nsIToolkitChromeRegistry"); 29 30 const LOAD_FROM_CACHE = nsIRequest.LOAD_FROM_CACHE; 31 const LOAD_BYPASS_LOCAL_CACHE_IF_BUSY = nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY; 32 33 const NS_BINDING_ABORTED = 0x804b0002; 34 35 // ********************************************************************************************* // 36 37 Firebug.SourceCache = function(context) 38 { 39 this.context = context; 40 this.cache = {}; 41 }; 42 43 Firebug.SourceCache.prototype = Obj.extend(new Firebug.Listener(), 44 { 45 isCached: function(url) 46 { 47 return (this.cache[url] ? true : false); 48 }, 49 50 loadText: function(url, method, file) 51 { 52 var lines = this.load(url, method, file); 53 return lines ? lines.join("") : null; 54 }, 55 56 load: function(url, method, file) 57 { 58 if (FBTrace.DBG_CACHE) 59 { 60 FBTrace.sysout("sourceCache.load: " + url); 61 62 if (!this.cache.hasOwnProperty(url) && this.cache[url]) 63 FBTrace.sysout("sourceCache.load; ERROR - hasOwnProperty returns false, " + 64 "but the URL is cached: " + url, this.cache[url]); 65 } 66 67 // xxxHonza: sometimes hasOwnProperty return false even if the URL is obviously there. 68 //if (this.cache.hasOwnProperty(url)) 69 var response = this.cache[this.removeAnchor(url)]; 70 if (response) 71 return response; 72 73 if (FBTrace.DBG_CACHE) 74 { 75 var urls = []; 76 for (var prop in this.cache) 77 urls.push(prop); 78 79 FBTrace.sysout("sourceCache.load: Not in the Firebug internal cache", urls); 80 } 81 82 var d = Url.splitDataURL(url); //TODO the RE should not have baseLine 83 if (d) 84 { 85 var src = d.encodedContent; 86 var data = decodeURIComponent(src); 87 var lines = Str.splitLines(data) 88 this.cache[url] = lines; 89 90 return lines; 91 } 92 93 var j = Url.reJavascript.exec(url); 94 if (j) 95 { 96 var src = url.substring(Url.reJavascript.lastIndex); 97 var lines = Str.splitLines(src); 98 this.cache[url] = lines; 99 100 return lines; 101 } 102 103 var c = Url.reChrome.test(url); 104 if (c) 105 { 106 if (Firebug.filterSystemURLs) 107 return ["Filtered chrome url "+url]; // ignore chrome 108 109 // If the chrome.manifest has xpcnativewrappers=no, platform munges the url 110 var reWrapperMunge = /(\S*)\s*->\s*(\S*)/; 111 var m = reWrapperMunge.exec(url); 112 if (m) 113 { 114 url = m[2]; 115 116 if (FBTrace.DBG_CACHE) 117 { 118 FBTrace.sysout("sourceCache found munged xpcnativewrapper url " + 119 "and set it to " + url + " m " + m + " m[0]:" + m[0] + " [1]" + 120 m[1], m); 121 } 122 } 123 124 var chromeURI = Url.makeURI(url); 125 if (!chromeURI) 126 { 127 if (FBTrace.DBG_CACHE) 128 FBTrace.sysout("sourceCache.load failed to convert chrome to local: " + url); 129 130 return ["sourceCache failed to make URI from " + url]; 131 } 132 133 var localURI = chromeReg.convertChromeURL(chromeURI); 134 if (FBTrace.DBG_CACHE) 135 FBTrace.sysout("sourceCache.load converting chrome to local: " + url, 136 " -> "+localURI.spec); 137 138 return this.loadFromLocal(localURI.spec); 139 } 140 141 c = Url.reFile.test(url); 142 if (c) 143 { 144 return this.loadFromLocal(url); 145 } 146 147 if (Str.hasPrefix(url, 'resource://')) 148 { 149 var fileURL = Url.resourceToFile(url); 150 return this.loadFromLocal(url); 151 } 152 153 // Unfortunately, the URL isn't available, so let's try to use FF cache. 154 // Note that an additional network request to the server could be made 155 // in this method (a double-load). 156 return this.loadFromCache(url, method, file); 157 }, 158 159 store: function(url, text) 160 { 161 var tempURL = this.removeAnchor(url); 162 163 if (FBTrace.DBG_CACHE) 164 FBTrace.sysout("sourceCache for " + this.context.getName() + " store url=" + 165 url + ((tempURL != url) ? " -> " + tempURL : ""), text); 166 167 var lines = Str.splitLines(text); 168 return this.storeSplitLines(tempURL, lines); 169 }, 170 171 removeAnchor: function(url) 172 { 173 if (FBTrace.DBG_ERRORS && !url) 174 FBTrace.sysout("sourceCache.removeAnchor; ERROR url must not be null"); 175 176 var index = url ? url.indexOf("#") : -1; 177 if (index < 0) 178 return url; 179 180 return url.substr(0, index); 181 }, 182 183 loadFromLocal: function(url) 184 { 185 if (FBTrace.DBG_CACHE) 186 FBTrace.sysout("tabCache.loadFromLocal url: " + url); 187 188 // if we get this far then we have either a file: or chrome: url converted to file: 189 var src = Http.getResource(url); 190 if (src) 191 { 192 var lines = Str.splitLines(src); 193 194 // Don't cache locale files to get latest version (issue 1328) 195 // Local files can be currently fetched any time. 196 //this.cache[url] = lines; 197 198 return lines; 199 } 200 }, 201 202 loadFromCache: function(url, method, file) 203 { 204 if (FBTrace.DBG_CACHE) FBTrace.sysout("sourceCache.loadFromCache url:"+url); 205 206 var doc = this.context.window.document; 207 if (doc) 208 var charset = doc.characterSet; 209 else 210 var charset = "UTF-8"; 211 212 var channel; 213 try 214 { 215 channel = ioService.newChannel(url, null, null); 216 channel.loadFlags |= LOAD_FROM_CACHE | LOAD_BYPASS_LOCAL_CACHE_IF_BUSY; 217 218 if (method && (channel instanceof nsIHttpChannel)) 219 { 220 var httpChannel = Xpcom.QI(channel, nsIHttpChannel); 221 httpChannel.requestMethod = method; 222 } 223 } 224 catch (exc) 225 { 226 if (FBTrace.DBG_CACHE) 227 FBTrace.sysout("sourceCache for url:" + url + " window=" + 228 this.context.window.location.href + " FAILS:", exc); 229 return; 230 } 231 232 if (url == this.context.browser.contentWindow.location.href) 233 { 234 if (FBTrace.DBG_CACHE) 235 FBTrace.sysout("sourceCache.load content window href"); 236 237 if (channel instanceof nsIUploadChannel) 238 { 239 var postData = getPostStream(this.context); 240 if (postData) 241 { 242 var uploadChannel = Xpcom.QI(channel, nsIUploadChannel); 243 uploadChannel.setUploadStream(postData, "", -1); 244 245 if (FBTrace.DBG_CACHE) 246 FBTrace.sysout("sourceCache.load uploadChannel set"); 247 } 248 } 249 250 if (channel instanceof nsICachingChannel) 251 { 252 var cacheChannel = Xpcom.QI(channel, nsICachingChannel); 253 cacheChannel.cacheKey = getCacheKey(this.context); 254 if (FBTrace.DBG_CACHE) 255 FBTrace.sysout("sourceCache.load cacheChannel key" + cacheChannel.cacheKey); 256 } 257 } 258 else if ((method == "PUT" || method == "POST") && file) 259 { 260 if (channel instanceof nsIUploadChannel) 261 { 262 // In case of PUT and POST, don't forget to use the original body. 263 var postData = getPostText(file, this.context); 264 if (postData) 265 { 266 var postDataStream = Http.getInputStreamFromString(postData); 267 var uploadChannel = Xpcom.QI(channel, nsIUploadChannel); 268 uploadChannel.setUploadStream(postDataStream, 269 "application/x-www-form-urlencoded", -1); 270 271 if (FBTrace.DBG_CACHE) 272 FBTrace.sysout("sourceCache.load uploadChannel set"); 273 } 274 } 275 } 276 277 var stream; 278 try 279 { 280 if (FBTrace.DBG_CACHE) 281 FBTrace.sysout("sourceCache.load url:" + url + " with charset" + charset); 282 283 stream = channel.open(); 284 } 285 catch (exc) 286 { 287 if (FBTrace.DBG_ERRORS) 288 { 289 var isCache = (channel instanceof nsICachingChannel) ? 290 "nsICachingChannel" : "NOT caching channel"; 291 var isUp = (channel instanceof nsIUploadChannel) ? 292 "nsIUploadChannel" : "NOT nsIUploadChannel"; 293 294 FBTrace.sysout(url + " vs " + this.context.browser.contentWindow.location.href + 295 " and " + isCache + " " + isUp); 296 FBTrace.sysout("sourceCache.load fails channel.open for url=" + url + 297 " cause:", exc); 298 FBTrace.sysout("sourceCache.load fails channel=", channel); 299 } 300 301 return ["sourceCache.load FAILS for url=" + url, exc.toString()]; 302 } 303 304 try 305 { 306 var data = Http.readFromStream(stream, charset); 307 var lines = Str.splitLines(data); 308 this.cache[url] = lines; 309 return lines; 310 } 311 catch (exc) 312 { 313 if (FBTrace.DBG_ERRORS) 314 FBTrace.sysout("sourceCache.load FAILS, url="+url, exc); 315 return ["sourceCache.load FAILS for url="+url, exc.toString()]; 316 } 317 finally 318 { 319 stream.close(); 320 } 321 }, 322 323 storeSplitLines: function(url, lines) 324 { 325 if (FBTrace.DBG_CACHE) 326 { 327 FBTrace.sysout("sourceCache for window=" + this.context.getName() + 328 " store url=" + url); 329 } 330 331 return this.cache[url] = lines; 332 }, 333 334 invalidate: function(url) 335 { 336 url = this.removeAnchor(url); 337 338 if (FBTrace.DBG_CACHE) 339 FBTrace.sysout("sourceCache.invalidate; " + url); 340 341 delete this.cache[url]; 342 }, 343 344 getLine: function(url, lineNo) 345 { 346 var lines; 347 348 try 349 { 350 lines = this.load(url); 351 } 352 catch (e) 353 { 354 if (FBTrace.DBG_ERRORS) 355 FBTrace.sysout("sourceCache.getLine; EXCEPTION " + e, e); 356 } 357 358 if (!lines) 359 return "(no source for " + url + ")"; 360 361 if (lineNo <= lines.length) 362 { 363 return lines[lineNo-1]; 364 } 365 else 366 { 367 return (lines.length == 1) ? 368 lines[0] : "(" + lineNo + " out of range " + lines.length + ")"; 369 } 370 } 371 }); 372 373 // xxxHonza getPostText and Http.readPostTextFromRequest are copied from 374 // net.js. These functions should be removed when this cache is 375 // refactored due to the double-load problem. 376 function getPostText(file, context) 377 { 378 if (!file.postText) 379 file.postText = Http.readPostTextFromPage(file.href, context); 380 381 if (!file.postText) 382 file.postText = Http.readPostTextFromRequest(file.request, context); 383 384 return file.postText; 385 } 386 387 // ********************************************************************************************* // 388 389 function getPostStream(context) 390 { 391 try 392 { 393 var webNav = context.browser.webNavigation; 394 var descriptor = Xpcom.QI(webNav, Ci.nsIWebPageDescriptor).currentDescriptor; 395 var entry = Xpcom.QI(descriptor, Ci.nsISHEntry); 396 397 if (entry.postData) 398 { 399 // Seek to the beginning, or it will probably start reading at the end 400 var postStream = Xpcom.QI(entry.postData, Ci.nsISeekableStream); 401 postStream.seek(0, 0); 402 return postStream; 403 } 404 } 405 catch (exc) 406 { 407 } 408 } 409 410 function getCacheKey(context) 411 { 412 try 413 { 414 var webNav = context.browser.webNavigation; 415 var descriptor = Xpcom.QI(webNav, Ci.nsIWebPageDescriptor).currentDescriptor; 416 var entry = Xpcom.QI(descriptor, Ci.nsISHEntry); 417 return entry.cacheKey; 418 } 419 catch (exc) 420 { 421 } 422 } 423 424 // ********************************************************************************************* // 425 // Registration 426 427 return Firebug.SourceCache; 428 429 // ********************************************************************************************* // 430 }); 431