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