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