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