Import from 1.9a8 tarball
[mozilla-extra.git] / extensions / venkman / resources / content / venkman-jsdurl.js
blobb4a804a62f5303c1fea02cd80d85f5e114fb885f
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * ***** BEGIN LICENSE BLOCK *****
4  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5  *
6  * The contents of this file are subject to the Mozilla Public License Version
7  * 1.1 (the "License"); you may not use this file except in compliance with
8  * the License. You may obtain a copy of the License at
9  * http://www.mozilla.org/MPL/
10  *
11  * Software distributed under the License is distributed on an "AS IS" basis,
12  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13  * for the specific language governing rights and limitations under the
14  * License.
15  *
16  * The Original Code is The JavaScript Debugger.
17  *
18  * The Initial Developer of the Original Code is
19  * Netscape Communications Corporation.
20  * Portions created by the Initial Developer are Copyright (C) 1998
21  * the Initial Developer. All Rights Reserved.
22  *
23  * Contributor(s):
24  *   Robert Ginda, <rginda@netscape.com>, original author
25  *
26  * Alternatively, the contents of this file may be used under the terms of
27  * either the GNU General Public License Version 2 or later (the "GPL"), or
28  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29  * in which case the provisions of the GPL or the LGPL are applicable instead
30  * of those above. If you wish to allow use of your version of this file only
31  * under the terms of either the GPL or the LGPL, and not to allow others to
32  * use your version of this file under the terms of the MPL, indicate your
33  * decision by deleting the provisions above and replace them with the notice
34  * and other provisions required by the GPL or the LGPL. If you do not delete
35  * the provisions above, a recipient may use your version of this file under
36  * the terms of any one of the MPL, the GPL or the LGPL.
37  *
38  * ***** END LICENSE BLOCK ***** */
40 const JSD_URL_SCHEME       = "x-jsd:";
41 const JSD_URL_PREFIX       = /x-jsd:/i;
42 const JSD_SCHEME_LEN       = JSD_URL_SCHEME.length;
44 const JSD_SERVICE_HELP     = "help";
45 const JSD_SERVICE_SOURCE   = "source";
46 const JSD_SERVICE_PPRINT   = "pprint";
47 const JSD_SERVICE_PPBUFFER = "ppbuffer";
49 function initJSDURL()
50 {   
51     var prefs =
52         [
53          ["services.help.css", "chrome://venkman/skin/venkman-help.css"],
54          ["services.help.template", "chrome://venkman/locale/venkman-help.tpl"],
55          ["services.source.css", "chrome://venkman/skin/venkman-source.css"],
56          ["services.source.colorize", true],
57          ["services.source.colorizeLimit", 1500]
58         ];
60     console.prefManager.addPrefs(prefs);
64  * x-jsd:<service>[?[<property>=<value>[(&<property>=<value>) ...]]][#anchor]
65  *
66  * <service> is one of...
67  *
68  *   x-jsd:help   - Help system
69  *                   properties are:
70  *                     search   - text to search for
71  *                     within   - bitmap of fields to search within
72  *                                 0x01 - search in command names
73  *                                 0x02 - search in ui labels
74  *                                 0x04 - search in help text
75  *
76  *   x-jsd:source - Source text
77  *                   properties are:
78  *                     url      - source url
79  *                     instance - index of source instance
80  *
81  *   x-jsd:pprint - Pretty Printed source text
82  *                   properties are:
83  *                     script   - tag of script to pretty print
84  *
85  *   x-jsd:ppbuffer - URL of a function created internally for pretty printing.
86  *                    You may come across this in jsdIScript objects, but it
87  *                    should not be used elsewhere.
88  *                     properties are:
89  *                      type - "function" or "script"
90  */
92 console.parseJSDURL = parseJSDURL;
93 function parseJSDURL (url)
95     var ary;
96     
97     if (url.search(JSD_URL_PREFIX) != 0)
98         return null;
99     
100     ary = url.substr(JSD_SCHEME_LEN).match(/([^?#]+)(?:\?(.*))?/);
101     if (!ary)
102         return null;
104     var parseResult = new Object();
105     parseResult.spec = url;
106     parseResult.service = ary[1].toLowerCase();
107     var rest = ary[2];
108     if (rest)
109     {
110         ary = rest.match(/([^&#]+)/);
112         while (ary)
113         {
114             rest = RegExp.rightContext.substr(1);
115             var assignment = ary[1];
116             ary = assignment.match(/(.+)=(.*)/);
117             if (ASSERT(ary, "error parsing ``" + assignment + "'' from " + url))
118             {
119                 var name = decodeURIComponent(ary[1]);
120                 /* only set the property the first time we see it */
121                 if (arrayHasElementAt(ary, 2) && !(name in parseResult))
122                     parseResult[name] = decodeURIComponent(ary[2]);
123             }
124             ary = rest.match(/([^&#]+)/);
125         }
126     }
127     
128     //dd (dumpObjectTree(parseResult));
129     return parseResult;
132 console.loadServiceTemplate =
133 function con_loadservicetpl (name, sections, callback)
135     function onComplete (data, url, status)
136     {
137         if (status == Components.results.NS_OK)
138         {
139             var tpl = parseSections (data, sections);
140             for (var p in sections)
141             {
142                 if (!(p in tpl))
143                 {
144                     display (getMsg (MSN_ERR_NO_SECTION, [sections[p], url]),
145                              MT_ERROR);
146                     callback(name, Components.results.NS_ERROR_FAILURE);
147                     return;
148                 }
149             }
150             console.serviceTemplates[name] = tpl;
151         }
152         callback(name, status);
153     };
154         
155     if (name in console.serviceTemplates)
156     {
157         callback(name, Components.results.NS_OK);
158         return;
159     }
161     var prefName = "services." + name + ".template";
162     if (!(prefName in console.prefs))
163     {
164         display (getMsg (MSN_ERR_NO_TEMPLATE, prefName), MT_ERROR);
165         callback(name, Components.results.NS_ERROR_FILE_NOT_FOUND);
166         return;
167     }
168     
169     var url = console.prefs[prefName];
170     loadURLAsync (url, { onComplete: onComplete });
172         
173 console.asyncOpenJSDURL = asyncOpenJSDURL;
174 function asyncOpenJSDURL (channel, streamListener, context)
175 {    
176     function onTemplateLoaded (name, status)
177     {
178         if (status != Components.results.NS_OK)
179         {
180             response.start();
181             response.append(getMsg(MSN_JSDURL_ERRPAGE,
182                                    [safeHTML(url),
183                                     getMsg(MSN_ERR_JSDURL_TEMPLATE, name)]));
184             response.end();
185         }
186         else
187         {
188             tryService();
189         }
190     };
191     
192     function tryService ()
193     {
194         var serviceObject = console.services[service];
195         if ("requiredTemplates" in serviceObject)
196         {
197             for (var i = 0; i < serviceObject.requiredTemplates.length; ++i)
198             {
199                 var def = serviceObject.requiredTemplates[i];
200                 if (!(def[0] in console.serviceTemplates))
201                 {
202                     console.loadServiceTemplate (def[0], def[1],
203                                                  onTemplateLoaded);
204                     return;
205                 }
206             }                
207         }
209         console.services[service](response, parseResult);
210     };
212     var url = channel.URI.spec;
213     var response = new JSDResponse (channel, streamListener, context);
214     var parseResult = parseJSDURL (url);
215     if (!parseResult)
216     {
217         response.start();
218         response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(url),
219                                                     MSG_ERR_JSDURL_PARSE]));
220         response.end();
221         return;
222     }
224     var service = parseResult.service.toLowerCase();
225     if (!(service in console.services))
226         service = "unknown";
228     tryService();
231 console.serviceTemplates = new Object();
232 console.services = new Object();
234 console.services["unknown"] =
235 function svc_nosource (response, parsedURL)
237     response.start();
238     response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec),
239                                                 MSG_ERR_JSDURL_NOSERVICE]));
240     response.end();
243 console.services["ppbuffer"] =
244 function svc_nosource (response)
246     response.start();
247     response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec),
248                                                 MSG_ERR_JSDURL_NOSOURCE]));
249     response.end();
252 console.services["help"] =
253 function svc_help (response, parsedURL)
255     const CHUNK_DELAY = 100;
256     const CHUNK_SIZE  = 150;
257     
258     function processHelpChunk (start)
259     {
260         var stop = Math.min (commandList.length, start + CHUNK_SIZE);
262         for (var i = start; i < stop; ++i)
263         {
264             command = commandList[i];
265             if (!("htmlHelp" in command))
266             {
267                 function replaceBold (str, p1)
268                 {
269                     return "<b>" + p1 + "</b>";
270                 };
271                 
272                 function replaceParam (str, p1)
273                 {
274                     return "&lt;<span class='param'>" + p1 + "</span>&gt;";
275                 };
276                 
277                 function replaceCommand (str, p1)
278                 {
279                     if (p1.indexOf(" ") != -1)
280                     {
281                         var ary = p1.split (" ");
282                         for (var i = 0; i < ary.length; ++i)
283                         {
284                             ary[i] = replaceCommand (null, ary[i]);
285                         }
287                         return ary.join (" ");
288                     }
289                     
290                     if (p1 != command.name &&
291                         (p1 in console.commandManager.commands))
292                     {
293                         return ("<a class='command-name' " +
294                                 "href='" + JSD_URL_SCHEME + "help?search=" +
295                                 p1 + "'>" + p1 + "</a>");
296                     }
298                     return "<tt>" + p1 + "</tt>";
299                 };
300                     
301                 
302                 var htmlUsage = command.usage.replace(/<([^\s>]+)>/g,
303                                                       replaceParam);
304                 var htmlDesc = command.help.replace(/<([^\s>]+)>/g,
305                                                     replaceParam);
306                 htmlDesc = htmlDesc.replace (/\*([^\*]+)\*/g, replaceBold);
307                 htmlDesc = htmlDesc.replace (/\|([^\|]+)\|/g, replaceCommand);
309                 // remove trailing access key (non en-US locales) and ...
310                 var trimmedLabel = 
311                     command.labelstr.replace(/(\([a-zA-Z]\))?(\.\.\.)?$/, "");
313                 vars = {
314                     "\\$command-name": command.name,
315                     "\\$ui-label-safe": encodeURIComponent(trimmedLabel),
316                     "\\$ui-label": fromUnicode(command.labelstr,
317                                                MSG_REPORT_CHARSET),
318                     "\\$params": fromUnicode(htmlUsage, MSG_REPORT_CHARSET),
319                     "\\$key": command.keystr,
320                     "\\$desc": fromUnicode(htmlDesc, MSG_REPORT_CHARSET)
321                 };
322                 
323                 command.htmlHelp = replaceStrings (section, vars);
324             }
325             
326             response.append(command.htmlHelp);
327         }
329         if (i != commandList.length)
330         {
331             setTimeout (processHelpChunk, CHUNK_DELAY, i);
332         }
333         else
334         {
335             response.append(tpl["footer"]);
336             response.end();
337         }
338     };
339     
340     function compare (a, b)
341     {
342         if (parsedURL.within & WITHIN_LABEL)
343         {
344             a = a.labelstr.toLowerCase();
345             b = b.labelstr.toLowerCase();
346         }
347         else
348         {
349             a = a.name;
350             b = b.name;
351         }    
353         if (a == b)
354             return 0;
356         if (a > b)
357             return 1;
359         return -1;
360     };
361     
362     var command;
363     var commandList = new Array();
364     var hasSearched;
365     var tpl = console.serviceTemplates["help"];
367     var WITHIN_NAME     = 0x01;
368     var WITHIN_LABEL    = 0x02;
369     var WITHIN_DESC     = 0x04;
371     if ("search" in parsedURL)
372     {
373         try
374         {
375             parsedURL.search = new RegExp (parsedURL.search, "i");
376         }
377         catch (ex)
378         {
379             response.start();
380             response.append(getMsg(MSN_JSDURL_ERRPAGE,
381                                    [parsedURL.spec, MSG_ERR_JSDURL_SEARCH]));
382             response.end();
383             return;
384         }
385         
386         dd ("searching for " + parsedURL.search);
387         
388         if (!("within" in parsedURL) ||
389             !((parsedURL.within = parseInt(parsedURL.within)) & 0x07))
390         {
391             parsedURL.within = WITHIN_NAME;
392         }
393         
394         for (var c in console.commandManager.commands)
395         {
396             command = console.commandManager.commands[c];
397             if ((parsedURL.within & WITHIN_NAME) &&
398                 command.name.search(parsedURL.search) != -1)
399             {
400                 commandList.push (command);
401             }
402             else if ((parsedURL.within & WITHIN_LABEL) &&
403                      command.labelstr.search(parsedURL.search) != -1)
404             {
405                 commandList.push (command);
406             }
407             else if ((parsedURL.within & WITHIN_DESC) &&
408                      command.help.search(parsedURL.search) != -1)
409             {
410                 commandList.push (command);
411             }
412         }
414         hasSearched = commandList.length > 0 ? "true" : "false";
415     }
416     else
417     {
418         commandList = console.commandManager.list ("", CMD_CONSOLE);
419         hasSearched = "false";
420     }
422     commandList.sort(compare);
423     
424     response.start();
426     var vars = {
427         "\\$css": console.prefs["services.help.css"],
428         "\\$match-count": commandList.length,
429         "\\$has-searched": hasSearched,
430         "\\$report-charset": MSG_REPORT_CHARSET
431     };
432             
433     response.append(replaceStrings(tpl["header"], vars));
435     if (commandList.length == 0)
436     {
437         response.append(tpl["nomatch"]);
438         response.append(tpl["footer"]);
439         response.end();
440     }
441     else
442     {
443         var section = tpl["command"];
444         processHelpChunk(0)
445     }
448 console.services["help"].requiredTemplates =
450  ["help", { "header"  : /@-header-end/mi,
451             "command" : /@-command-end/mi,
452             "nomatch" : /@-nomatch-end/mi,
453             "footer"  : 0 }
457 const OTHER   = 0;
458 const COMMENT = 1;
459 const STRING1 = 2;
460 const STRING2 = 3;
461 const WORD    = 4;
462 const NUMBER  = 5;
463 const REGEXP  = 6;
465 var keywords = {
466     "abstract": 1, "boolean": 1, "break": 1, "byte": 1, "case": 1, "catch": 1,
467     "char": 1, "class": 1, "const": 1, "continue": 1, "debugger": 1,
468     "default": 1, "delete": 1, "do": 1, "double": 1, "else": 1, "enum": 1,
469     "export": 1, "export": 1, "extends": 1, "false": 1, "final": 1,
470     "finally": 1, "float": 1, "for": 1, "function": 1, "goto": 1, "if": 1,
471     "implements": 1, "import": 1, "in": 1, "instanceof": 1, "int": 1,
472     "interface": 1, "long": 1, "native": 1, "new": 1, "null": 1,
473     "package": 1, "private": 1, "protected": 1, "public": 1, "return": 1,
474     "short": 1, "static": 1, "switch": 1, "synchronized": 1, "this": 1,
475     "throw": 1, "throws": 1, "transient": 1, "true": 1, "try": 1,
476     "typeof": 1, "var": 1, "void": 1, "while": 1, "with": 1
479 var specialChars = /[&<>]/g;
481 var wordStart = /[\w\\\$]/;
482 var numberStart = /[\d]/;
484 var otherEnd = /[\w\$\"\']|\\|\//;
485 var wordEnd = /[^\w\$]/;
486 var string1End = /\'/;
487 var string2End = /\"/;
488 var commentEnd = /\*\//;
489 var numberEnd = /[^\d\.]/;
491 function escapeSpecial (p1)
493     switch (p1)
494     {
495         case "&":
496             return "&amp;";
497         case "<":
498             return "&lt;";
499         case ">":
500             return "&gt;";
501     }
503     return p1;
505     
506 function escapeSourceLine (line)
508     return { line: line.replace (specialChars, escapeSpecial),
509              previousState: 0 };
512 function colorizeSourceLine (line, previousState)
514     function closePhrase (phrase)
515     {
516         if (!phrase)
517         {
518             previousState = OTHER;
519             return;
520         }
522         switch (previousState)
523         {
524             case COMMENT:
525                 result += "<c>" + phrase.replace (specialChars, escapeSpecial) +
526                     "</c>";
527                 break;            
528             case STRING1:
529             case STRING2:
530                 result += "<t>" + phrase.replace (specialChars, escapeSpecial) +
531                     "</t>";
532                 break;
533             case WORD:
534                 if (phrase in keywords)
535                     result += "<k>" + phrase + "</k>";
536                 else
537                     result += phrase.replace (specialChars, escapeSpecial);
538                 break;
539             case OTHER:
540                 phrase = phrase.replace (specialChars, escapeSpecial);
541                 /* fall through */
542             case NUMBER:
543                 result += phrase;
544                 break;
545             case REGEXP:
546                 result += "<r>" + phrase.replace(specialChars, escapeSpecial) +
547                           "</r>";
548                 break;
549         }
550     };
551     
552     var result = "";
553     var pos;
554     var ch, ch2;
555     var expr;
556     
557     while (line.length > 0)
558     {
559         /* scan a line of text.  |pos| always one *past* the end of the
560          * phrase we just scanned. */
562         switch (previousState)
563         {
564             case OTHER:
565                 /* look for the end of an uncalssified token, like whitespace
566                  * or an operator. */
567                 pos = line.search (otherEnd);
568                 break;
570             case WORD:
571                 /* look for the end of something that qualifies as
572                  * an identifier. */
573                 pos = line.search(wordEnd);
574                 while (pos > -1 && line[pos] == "\\")
575                 {
576                     /* if we ended with a \ character, then the slash
577                      * and the character after it are part of this word.
578                      * the characters following may also be part of the
579                      * word. */
580                     pos += 2;
581                     var newPos = line.substr(pos).search(wordEnd);
582                     if (newPos > -1)
583                         pos += newPos;
584                 }
585                 break;
587             case STRING1:
588             case STRING2:
589                 /* look for the end of a single or double quoted string. */
590                 if (previousState == STRING1)
591                 {
592                     ch = "'";
593                     expr = string1End;
594                 }
595                 else
596                 {
597                     ch = "\"";
598                     expr = string2End;
599                 }
601                 if (line[0] == ch)
602                 {
603                     pos = 1;
604                 }
605                 else
606                 {
607                     pos = line.search (expr);
608                     if (pos > 0 && line[pos - 1] == "\\")
609                     {
610                         /* arg, the quote we found was escaped, fall back
611                          * to scanning a character at a time. */
612                         var done = false;
613                         for (pos = 0; !done && pos < line.length; ++pos)
614                         {
615                             if (line[pos] == "\\")
616                                 ++pos;
617                             else if (line[pos] == ch)
618                                 done = true;
619                         }        
620                     }
621                     else
622                     {
623                         if (pos != -1)
624                             ++pos;
625                     }
626                 }
627                 break;
629             case COMMENT:
630                 /* look for the end of a slash-star comment,
631                  * slash-slash comments are handled down below, because
632                  * we know for sure that it's the last phrase on this line.
633                  */
634                 pos = line.search (commentEnd);
635                 if (pos != -1)
636                     pos += 2;
637                 break;
639             case NUMBER:
640                 /* look for the end of a number */
641                 pos = line.search (numberEnd);
642                 break; 
644             case REGEXP:
645                 /* look for the end of the regexp */
646                 pos = line.substr(1).search("/") + 1;
647                 while (pos > 0 && line[pos - 1] == "\\")
648                 {
649                     /* if the previous char was \, we are escaped and need
650                      * to keep trying. */
651                     pos += 1;
652                     var newPos = line.substr(pos).search("/");
653                     if (newPos > -1)
654                         pos += newPos;
655                 }
656                 if (pos != -1)
657                     ++pos;
658                 break;
659         }
661         if (pos == -1)
662         {
663             /* couldn't find an end for the current state, close out the 
664              * rest of the line.
665              */
666             closePhrase(line);
667             line = "";
668         }
669         else
670         {
671             /* pos has a non -1 value, close out what we found, and move
672              * along. */
673             if (previousState == STRING1 || previousState == STRING2)
674             {
675                 /* strings are a special case because they actually are
676                  * considered to start *after* the leading quote,
677                  * and they end *before* the trailing quote. */
678                 if (pos == 1)
679                 {
680                     /* empty string */
681                     previousState = OTHER;
682                 }
683                 else
684                 {
685                     /* non-empty string, close out the contents of the
686                      * string. */
687                     closePhrase(line.substr (0, pos - 1));
688                     previousState = OTHER;
689                 }
691                 /* close the trailing quote. */
692                 result += line[pos - 1];
693             }
694             else
695             {
696                 /* non-string phrase, close the whole deal. */
697                 closePhrase(line.substr (0, pos));
698                 previousState = OTHER;
699             }
701             if (pos)
702                 line = line.substr (pos);
703         }
705         if (line)
706         {
707             /* figure out what the next token looks like. */
708             ch = line[0];
709             ch2 = (line.length > 1) ? line[1] : "";
710             
711             if (ch.search (wordStart) == 0)
712             {
713                 previousState = WORD;
714             }
715             else if (ch == "'")
716             {
717                 result += "'";
718                 line = line.substr(1);
719                 previousState = STRING1;
720             }
721             else if (ch == "\"")
722             {
723                 result += "\"";
724                 line = line.substr(1);
725                 previousState = STRING2;
726             }
727             else if (ch == "/" && ch2 == "*")
728             {
729                 previousState = COMMENT;
730             }
731             else if (ch == "/" && ch2 == "/")
732             {
733                 /* slash-slash comment, the last thing on this line. */
734                 previousState = COMMENT;
735                 closePhrase(line);
736                 previousState = OTHER;
737                 line = "";
738             }
739             else if (ch == "/")
740             {
741                 previousState = REGEXP;
742             }
743             else if (ch.search (numberStart) == 0)
744             {
745                 previousState = NUMBER;
746             }
747         }
748     }
750     return { previousState: previousState, line: result };
753 console.respondWithSourceText =
754 function con_respondsourcetext (response, sourceText)
756     const CHUNK_DELAY = 50;
757     const CHUNK_SIZE  = 250;
758     var sourceLines = sourceText.lines;
759     var resultSource;
760     var tenSpaces = "          ";
761     var maxDigits;
762     
763     var previousState = 0;
765     var mungeLine;
767     if (console.prefs["services.source.colorize"] && 
768         sourceLines.length <= console.prefs["services.source.colorizeLimit"])
769     {
770         mungeLine = colorizeSourceLine;
771     }
772     else
773     {
774         mungeLine = escapeSourceLine;
775     }
777     function processSourceChunk (start)
778     {
779         dd ("processSourceChunk " + start + " {");
780                     
781         var stop = Math.min (sourceLines.length, start + CHUNK_SIZE);
782         
783         for (var i = start; i < stop; ++i)
784         {
785             var padding;
786             if (i != 999)
787             {
788                 padding =
789                     tenSpaces.substr(0, maxDigits -
790                                      Math.floor(Math.log(i + 1) / Math.LN10));
791             }
792             else
793             {
794                 /* at exactly 1000, a rounding error gets us. */
795                 padding = tenSpaces.substr(0, maxDigits - 3);
796             }    
798             var isExecutable;
799             var marginContent;
800             if ("lineMap" in sourceText && i in sourceText.lineMap)
801             {
802                 if (sourceText.lineMap[i] & LINE_BREAKABLE)
803                 {
804                     isExecutable = "t";
805                     marginContent = " - ";
806                 }
807                 else
808                 {
809                     isExecutable = "f";
810                     marginContent = "   ";
811                 }
812             }
813             else
814             {
815                 isExecutable = "f";
816                 marginContent = "   ";
817             }
818             
819             var o = mungeLine(sourceLines[i], previousState);
820             var line = o.line;
821             previousState = o.previousState;
823             resultSource += "<line><margin x='" + isExecutable +"'>" +
824                 marginContent + "</margin>" +
825                 "<num>" + padding + (i + 1) +
826                 "</num> " + line + "</line>\n";
827                
828         }
830         if (i != sourceLines.length)
831         {
832             setTimeout (processSourceChunk, CHUNK_DELAY, i);
833         }
834         else
835         {
836             resultSource += "</source-listing>";
837             //resultSource += "</source-listing></body></html>";
838             response.append(resultSource);
839             response.end();
840             sourceText.markup = resultSource;
841             dd ("}");
842         }
844         dd ("}");
845     };
846     
847     if ("charset" in sourceText)
848         response.channel.contentCharset = sourceText.charset;
849     
850     if ("markup" in sourceText)
851     {
852         response.channel.contentType = "application/xml";
853         response.start();
854         response.append(sourceText.markup);
855         response.end();
856     }
857     else
858     {
859         maxDigits = Math.floor(Math.log(sourceLines.length) / Math.LN10) + 1;
860         dd ("OFF building response {");
861         response.channel.contentType = "application/xml";
862         resultSource = "<?xml version='1.0'";
863         //        if ("charset" in sourceText)
864         //    resultSource += " encoding=\"" + sourceText.charset + "\"";
865         resultSource += "?>\n" +
866             "<?xml-stylesheet type='text/css' href='" +
867             console.prefs["services.source.css"] + "' ?>\n" +
868             "<source-listing id='source-listing'>\n";
869         
870         /*
871           resultSource = "<html><head>\n" +
872           "<link rel='stylesheet' type='text/css' href='" +
873           console.prefs["services.source.css"] + "'><body>\n" +
874           "<source-listing id='source-listing'>\n";
875         */
876         response.start();
877         
878         processSourceChunk (0);
879     }
882 console.services["pprint"] =
883 function svc_pprint (response, parsedURL)
884 {    
885     var err;
886     
887     if (!("scriptWrapper" in parsedURL))
888     {
889         err = getMsg(MSN_ERR_REQUIRED_PARAM, "scriptWrapper");
890         response.start();
891         response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec),
892                                                     err]));
893         response.end();
894         return;
895     }
897     if (!(parsedURL.scriptWrapper in console.scriptWrappers))
898     {
899         err = getMsg(MSN_ERR_INVALID_PARAM,
900                          ["scriptWrapper", parsedURL.scriptWrapper]);
901         response.start();
902         response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec),
903                                                     err]));
904         response.end();
905         return;
906     }
908     var sourceText = console.scriptWrappers[parsedURL.scriptWrapper].sourceText;
909     console.respondWithSourceText (response, sourceText);
912 console.services["source"] =
913 function svc_source (response, parsedURL)
915     function onSourceTextLoaded (status)
916     {
917         if (status != Components.results.NS_OK)
918         {
919             response.start();
920             response.append(getMsg(MSN_JSDURL_ERRPAGE,
921                                    [safeHTML(parsedURL.spec), status]));
922             response.end();
923             display (getMsg (MSN_ERR_SOURCE_LOAD_FAILED,
924                              [parsedURL.spec, status]),
925                      MT_ERROR);
926             return;
927         }
929         console.respondWithSourceText (response, sourceText);
930     }
932     if (!("location" in parsedURL) || !parsedURL.location)
933     {
934         var err = getMsg(MSN_ERR_REQUIRED_PARAM, "location");
935         response.start();
936         response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec),
937                                                     err]));
938         response.end();
939         return;
940     }
941         
942     var sourceText;
943     var targetURL = parsedURL.location;
944     
945     if (targetURL in console.scriptManagers)
946     {
947         var scriptManager = console.scriptManagers[targetURL];
948         if ("instance" in parsedURL)
949         {
950             var instance =
951                 scriptManager.getInstanceBySequence(parsedURL.instance);
952             if (instance)
953                 sourceText = instance.sourceText;
954         }
955         
956         if (!sourceText)
957             sourceText = scriptManager.sourceText;
958     }
959     else
960     {
961         if (targetURL in console.files)
962             sourceText = console.files[targetURL];
963         else
964             sourceText = console.files[targetURL] = new SourceText (targetURL);
965     }
967     if (!sourceText)
968     {
969         response.start();
970         response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec),
971                                                     MSG_ERR_JSDURL_SOURCETEXT]));
972         response.end();
973         return;
974     }
976     if (!sourceText.isLoaded)
977         sourceText.loadSource (onSourceTextLoaded);
978     else
979         onSourceTextLoaded(Components.results.NS_OK);    
982 function JSDResponse (channel, streamListener, context)
984     this.hasStarted     = false;
985     this.hasEnded       = false;
986     this.channel        = channel;
987     this.streamListener = streamListener;
988     this.context        = context;
991 JSDResponse.prototype.start =
992 function jsdr_start()
994     if (!ASSERT(!this.hasStarted, "response already started"))
995         return;
996     
997     this.streamListener.onStartRequest (this.channel, this.context);
998     this.hasStarted = true;
1001 JSDResponse.prototype.append =
1002 function jsdr_append (str)
1004     //dd ("appending\n" + str);
1006     const STRING_STREAM_CTRID = "@mozilla.org/io/string-input-stream;1";
1007     const nsIStringInputStream = Components.interfaces.nsIStringInputStream;
1008     const I_LOVE_NECKO = 2152398850;
1010     var clazz = Components.classes[STRING_STREAM_CTRID];
1011     var stringStream = clazz.createInstance(nsIStringInputStream);
1013     var len = str.length;
1014     stringStream.setData (str, len);
1015     try
1016     {
1017         this.streamListener.onDataAvailable (this.channel, this.context, 
1018                                              stringStream, 0, len);
1019     }
1020     catch (ex)
1021     {
1022         if ("result" in ex && ex.result == I_LOVE_NECKO)
1023         {
1024             /* ignore this exception, it means the caller doesn't want the
1025              * data, or something.
1026              */
1027         }
1028         else
1029         {
1030             throw ex;
1031         }
1032     }
1035 JSDResponse.prototype.end =
1036 function jsdr_end ()
1038     if (!ASSERT(this.hasStarted, "response hasn't started"))
1039         return;
1040     
1041     if (!ASSERT(!this.hasEnded, "response has already ended"))
1042         return;
1043     
1044     var ok = Components.results.NS_OK;
1045     this.streamListener.onStopRequest (this.channel, this.context, ok);
1046     if (this.channel.loadGroup)
1047         this.channel.loadGroup.removeRequest (this.channel, null, ok);
1048     else
1049         dd ("channel had no load group");
1050     this.channel._isPending = false;
1051     this.hasEnded = true;
1052     //dd ("response ended");