1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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
16 * The Original Code is The JavaScript Debugger.
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.
24 * Robert Ginda, <rginda@netscape.com>, original author
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.
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";
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]
60 console.prefManager.addPrefs(prefs);
64 * x-jsd:<service>[?[<property>=<value>[(&<property>=<value>) ...]]][#anchor]
66 * <service> is one of...
68 * x-jsd:help - Help system
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
76 * x-jsd:source - Source text
79 * instance - index of source instance
81 * x-jsd:pprint - Pretty Printed source text
83 * script - tag of script to pretty print
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.
89 * type - "function" or "script"
92 console.parseJSDURL = parseJSDURL;
93 function parseJSDURL (url)
97 if (url.search(JSD_URL_PREFIX) != 0)
100 ary = url.substr(JSD_SCHEME_LEN).match(/([^?#]+)(?:\?(.*))?/);
104 var parseResult = new Object();
105 parseResult.spec = url;
106 parseResult.service = ary[1].toLowerCase();
110 ary = rest.match(/([^&#]+)/);
114 rest = RegExp.rightContext.substr(1);
115 var assignment = ary[1];
116 ary = assignment.match(/(.+)=(.*)/);
117 if (ASSERT(ary, "error parsing ``" + assignment + "'' from " + url))
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]);
124 ary = rest.match(/([^&#]+)/);
128 //dd (dumpObjectTree(parseResult));
132 console.loadServiceTemplate =
133 function con_loadservicetpl (name, sections, callback)
135 function onComplete (data, url, status)
137 if (status == Components.results.NS_OK)
139 var tpl = parseSections (data, sections);
140 for (var p in sections)
144 display (getMsg (MSN_ERR_NO_SECTION, [sections[p], url]),
146 callback(name, Components.results.NS_ERROR_FAILURE);
150 console.serviceTemplates[name] = tpl;
152 callback(name, status);
155 if (name in console.serviceTemplates)
157 callback(name, Components.results.NS_OK);
161 var prefName = "services." + name + ".template";
162 if (!(prefName in console.prefs))
164 display (getMsg (MSN_ERR_NO_TEMPLATE, prefName), MT_ERROR);
165 callback(name, Components.results.NS_ERROR_FILE_NOT_FOUND);
169 var url = console.prefs[prefName];
170 loadURLAsync (url, { onComplete: onComplete });
173 console.asyncOpenJSDURL = asyncOpenJSDURL;
174 function asyncOpenJSDURL (channel, streamListener, context)
176 function onTemplateLoaded (name, status)
178 if (status != Components.results.NS_OK)
181 response.append(getMsg(MSN_JSDURL_ERRPAGE,
183 getMsg(MSN_ERR_JSDURL_TEMPLATE, name)]));
192 function tryService ()
194 var serviceObject = console.services[service];
195 if ("requiredTemplates" in serviceObject)
197 for (var i = 0; i < serviceObject.requiredTemplates.length; ++i)
199 var def = serviceObject.requiredTemplates[i];
200 if (!(def[0] in console.serviceTemplates))
202 console.loadServiceTemplate (def[0], def[1],
209 console.services[service](response, parseResult);
212 var url = channel.URI.spec;
213 var response = new JSDResponse (channel, streamListener, context);
214 var parseResult = parseJSDURL (url);
218 response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(url),
219 MSG_ERR_JSDURL_PARSE]));
224 var service = parseResult.service.toLowerCase();
225 if (!(service in console.services))
231 console.serviceTemplates = new Object();
232 console.services = new Object();
234 console.services["unknown"] =
235 function svc_nosource (response, parsedURL)
238 response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec),
239 MSG_ERR_JSDURL_NOSERVICE]));
243 console.services["ppbuffer"] =
244 function svc_nosource (response)
247 response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec),
248 MSG_ERR_JSDURL_NOSOURCE]));
252 console.services["help"] =
253 function svc_help (response, parsedURL)
255 const CHUNK_DELAY = 100;
256 const CHUNK_SIZE = 150;
258 function processHelpChunk (start)
260 var stop = Math.min (commandList.length, start + CHUNK_SIZE);
262 for (var i = start; i < stop; ++i)
264 command = commandList[i];
265 if (!("htmlHelp" in command))
267 function replaceBold (str, p1)
269 return "<b>" + p1 + "</b>";
272 function replaceParam (str, p1)
274 return "<<span class='param'>" + p1 + "</span>>";
277 function replaceCommand (str, p1)
279 if (p1.indexOf(" ") != -1)
281 var ary = p1.split (" ");
282 for (var i = 0; i < ary.length; ++i)
284 ary[i] = replaceCommand (null, ary[i]);
287 return ary.join (" ");
290 if (p1 != command.name &&
291 (p1 in console.commandManager.commands))
293 return ("<a class='command-name' " +
294 "href='" + JSD_URL_SCHEME + "help?search=" +
295 p1 + "'>" + p1 + "</a>");
298 return "<tt>" + p1 + "</tt>";
302 var htmlUsage = command.usage.replace(/<([^\s>]+)>/g,
304 var htmlDesc = command.help.replace(/<([^\s>]+)>/g,
306 htmlDesc = htmlDesc.replace (/\*([^\*]+)\*/g, replaceBold);
307 htmlDesc = htmlDesc.replace (/\|([^\|]+)\|/g, replaceCommand);
309 // remove trailing access key (non en-US locales) and ...
311 command.labelstr.replace(/(\([a-zA-Z]\))?(\.\.\.)?$/, "");
314 "\\$command-name": command.name,
315 "\\$ui-label-safe": encodeURIComponent(trimmedLabel),
316 "\\$ui-label": fromUnicode(command.labelstr,
318 "\\$params": fromUnicode(htmlUsage, MSG_REPORT_CHARSET),
319 "\\$key": command.keystr,
320 "\\$desc": fromUnicode(htmlDesc, MSG_REPORT_CHARSET)
323 command.htmlHelp = replaceStrings (section, vars);
326 response.append(command.htmlHelp);
329 if (i != commandList.length)
331 setTimeout (processHelpChunk, CHUNK_DELAY, i);
335 response.append(tpl["footer"]);
340 function compare (a, b)
342 if (parsedURL.within & WITHIN_LABEL)
344 a = a.labelstr.toLowerCase();
345 b = b.labelstr.toLowerCase();
363 var commandList = new Array();
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)
375 parsedURL.search = new RegExp (parsedURL.search, "i");
380 response.append(getMsg(MSN_JSDURL_ERRPAGE,
381 [parsedURL.spec, MSG_ERR_JSDURL_SEARCH]));
386 dd ("searching for " + parsedURL.search);
388 if (!("within" in parsedURL) ||
389 !((parsedURL.within = parseInt(parsedURL.within)) & 0x07))
391 parsedURL.within = WITHIN_NAME;
394 for (var c in console.commandManager.commands)
396 command = console.commandManager.commands[c];
397 if ((parsedURL.within & WITHIN_NAME) &&
398 command.name.search(parsedURL.search) != -1)
400 commandList.push (command);
402 else if ((parsedURL.within & WITHIN_LABEL) &&
403 command.labelstr.search(parsedURL.search) != -1)
405 commandList.push (command);
407 else if ((parsedURL.within & WITHIN_DESC) &&
408 command.help.search(parsedURL.search) != -1)
410 commandList.push (command);
414 hasSearched = commandList.length > 0 ? "true" : "false";
418 commandList = console.commandManager.list ("", CMD_CONSOLE);
419 hasSearched = "false";
422 commandList.sort(compare);
427 "\\$css": console.prefs["services.help.css"],
428 "\\$match-count": commandList.length,
429 "\\$has-searched": hasSearched,
430 "\\$report-charset": MSG_REPORT_CHARSET
433 response.append(replaceStrings(tpl["header"], vars));
435 if (commandList.length == 0)
437 response.append(tpl["nomatch"]);
438 response.append(tpl["footer"]);
443 var section = tpl["command"];
448 console.services["help"].requiredTemplates =
450 ["help", { "header" : /@-header-end/mi,
451 "command" : /@-command-end/mi,
452 "nomatch" : /@-nomatch-end/mi,
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)
506 function escapeSourceLine (line)
508 return { line: line.replace (specialChars, escapeSpecial),
512 function colorizeSourceLine (line, previousState)
514 function closePhrase (phrase)
518 previousState = OTHER;
522 switch (previousState)
525 result += "<c>" + phrase.replace (specialChars, escapeSpecial) +
530 result += "<t>" + phrase.replace (specialChars, escapeSpecial) +
534 if (phrase in keywords)
535 result += "<k>" + phrase + "</k>";
537 result += phrase.replace (specialChars, escapeSpecial);
540 phrase = phrase.replace (specialChars, escapeSpecial);
546 result += "<r>" + phrase.replace(specialChars, escapeSpecial) +
557 while (line.length > 0)
559 /* scan a line of text. |pos| always one *past* the end of the
560 * phrase we just scanned. */
562 switch (previousState)
565 /* look for the end of an uncalssified token, like whitespace
567 pos = line.search (otherEnd);
571 /* look for the end of something that qualifies as
573 pos = line.search(wordEnd);
574 while (pos > -1 && line[pos] == "\\")
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
581 var newPos = line.substr(pos).search(wordEnd);
589 /* look for the end of a single or double quoted string. */
590 if (previousState == STRING1)
607 pos = line.search (expr);
608 if (pos > 0 && line[pos - 1] == "\\")
610 /* arg, the quote we found was escaped, fall back
611 * to scanning a character at a time. */
613 for (pos = 0; !done && pos < line.length; ++pos)
615 if (line[pos] == "\\")
617 else if (line[pos] == ch)
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.
634 pos = line.search (commentEnd);
640 /* look for the end of a number */
641 pos = line.search (numberEnd);
645 /* look for the end of the regexp */
646 pos = line.substr(1).search("/") + 1;
647 while (pos > 0 && line[pos - 1] == "\\")
649 /* if the previous char was \, we are escaped and need
652 var newPos = line.substr(pos).search("/");
663 /* couldn't find an end for the current state, close out the
671 /* pos has a non -1 value, close out what we found, and move
673 if (previousState == STRING1 || previousState == STRING2)
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. */
681 previousState = OTHER;
685 /* non-empty string, close out the contents of the
687 closePhrase(line.substr (0, pos - 1));
688 previousState = OTHER;
691 /* close the trailing quote. */
692 result += line[pos - 1];
696 /* non-string phrase, close the whole deal. */
697 closePhrase(line.substr (0, pos));
698 previousState = OTHER;
702 line = line.substr (pos);
707 /* figure out what the next token looks like. */
709 ch2 = (line.length > 1) ? line[1] : "";
711 if (ch.search (wordStart) == 0)
713 previousState = WORD;
718 line = line.substr(1);
719 previousState = STRING1;
724 line = line.substr(1);
725 previousState = STRING2;
727 else if (ch == "/" && ch2 == "*")
729 previousState = COMMENT;
731 else if (ch == "/" && ch2 == "/")
733 /* slash-slash comment, the last thing on this line. */
734 previousState = COMMENT;
736 previousState = OTHER;
741 previousState = REGEXP;
743 else if (ch.search (numberStart) == 0)
745 previousState = NUMBER;
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;
763 var previousState = 0;
767 if (console.prefs["services.source.colorize"] &&
768 sourceLines.length <= console.prefs["services.source.colorizeLimit"])
770 mungeLine = colorizeSourceLine;
774 mungeLine = escapeSourceLine;
777 function processSourceChunk (start)
779 dd ("processSourceChunk " + start + " {");
781 var stop = Math.min (sourceLines.length, start + CHUNK_SIZE);
783 for (var i = start; i < stop; ++i)
789 tenSpaces.substr(0, maxDigits -
790 Math.floor(Math.log(i + 1) / Math.LN10));
794 /* at exactly 1000, a rounding error gets us. */
795 padding = tenSpaces.substr(0, maxDigits - 3);
800 if ("lineMap" in sourceText && i in sourceText.lineMap)
802 if (sourceText.lineMap[i] & LINE_BREAKABLE)
805 marginContent = " - ";
819 var o = mungeLine(sourceLines[i], previousState);
821 previousState = o.previousState;
823 resultSource += "<line><margin x='" + isExecutable +"'>" +
824 marginContent + "</margin>" +
825 "<num>" + padding + (i + 1) +
826 "</num> " + line + "</line>\n";
830 if (i != sourceLines.length)
832 setTimeout (processSourceChunk, CHUNK_DELAY, i);
836 resultSource += "</source-listing>";
837 //resultSource += "</source-listing></body></html>";
838 response.append(resultSource);
840 sourceText.markup = resultSource;
847 if ("charset" in sourceText)
848 response.channel.contentCharset = sourceText.charset;
850 if ("markup" in sourceText)
852 response.channel.contentType = "application/xml";
854 response.append(sourceText.markup);
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";
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";
878 processSourceChunk (0);
882 console.services["pprint"] =
883 function svc_pprint (response, parsedURL)
887 if (!("scriptWrapper" in parsedURL))
889 err = getMsg(MSN_ERR_REQUIRED_PARAM, "scriptWrapper");
891 response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec),
897 if (!(parsedURL.scriptWrapper in console.scriptWrappers))
899 err = getMsg(MSN_ERR_INVALID_PARAM,
900 ["scriptWrapper", parsedURL.scriptWrapper]);
902 response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec),
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)
917 if (status != Components.results.NS_OK)
920 response.append(getMsg(MSN_JSDURL_ERRPAGE,
921 [safeHTML(parsedURL.spec), status]));
923 display (getMsg (MSN_ERR_SOURCE_LOAD_FAILED,
924 [parsedURL.spec, status]),
929 console.respondWithSourceText (response, sourceText);
932 if (!("location" in parsedURL) || !parsedURL.location)
934 var err = getMsg(MSN_ERR_REQUIRED_PARAM, "location");
936 response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec),
943 var targetURL = parsedURL.location;
945 if (targetURL in console.scriptManagers)
947 var scriptManager = console.scriptManagers[targetURL];
948 if ("instance" in parsedURL)
951 scriptManager.getInstanceBySequence(parsedURL.instance);
953 sourceText = instance.sourceText;
957 sourceText = scriptManager.sourceText;
961 if (targetURL in console.files)
962 sourceText = console.files[targetURL];
964 sourceText = console.files[targetURL] = new SourceText (targetURL);
970 response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec),
971 MSG_ERR_JSDURL_SOURCETEXT]));
976 if (!sourceText.isLoaded)
977 sourceText.loadSource (onSourceTextLoaded);
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"))
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);
1017 this.streamListener.onDataAvailable (this.channel, this.context,
1018 stringStream, 0, len);
1022 if ("result" in ex && ex.result == I_LOVE_NECKO)
1024 /* ignore this exception, it means the caller doesn't want the
1025 * data, or something.
1035 JSDResponse.prototype.end =
1036 function jsdr_end ()
1038 if (!ASSERT(this.hasStarted, "response hasn't started"))
1041 if (!ASSERT(!this.hasEnded, "response has already ended"))
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);
1049 dd ("channel had no load group");
1050 this.channel._isPending = false;
1051 this.hasEnded = true;
1052 //dd ("response ended");