/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is The JavaScript Debugger. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Robert Ginda, , original author * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ const JSD_URL_SCHEME = "x-jsd:"; const JSD_URL_PREFIX = /x-jsd:/i; const JSD_SCHEME_LEN = JSD_URL_SCHEME.length; const JSD_SERVICE_HELP = "help"; const JSD_SERVICE_SOURCE = "source"; const JSD_SERVICE_PPRINT = "pprint"; const JSD_SERVICE_PPBUFFER = "ppbuffer"; function initJSDURL() { var prefs = [ ["services.help.css", "chrome://venkman/skin/venkman-help.css"], ["services.help.template", "chrome://venkman/locale/venkman-help.tpl"], ["services.source.css", "chrome://venkman/skin/venkman-source.css"], ["services.source.colorize", true], ["services.source.colorizeLimit", 1500] ]; console.prefManager.addPrefs(prefs); } /* * x-jsd:[?[=[(&=) ...]]][#anchor] * * is one of... * * x-jsd:help - Help system * properties are: * search - text to search for * within - bitmap of fields to search within * 0x01 - search in command names * 0x02 - search in ui labels * 0x04 - search in help text * * x-jsd:source - Source text * properties are: * url - source url * instance - index of source instance * * x-jsd:pprint - Pretty Printed source text * properties are: * script - tag of script to pretty print * * x-jsd:ppbuffer - URL of a function created internally for pretty printing. * You may come across this in jsdIScript objects, but it * should not be used elsewhere. * properties are: * type - "function" or "script" */ console.parseJSDURL = parseJSDURL; function parseJSDURL (url) { var ary; if (url.search(JSD_URL_PREFIX) != 0) return null; ary = url.substr(JSD_SCHEME_LEN).match(/([^?#]+)(?:\?(.*))?/); if (!ary) return null; var parseResult = new Object(); parseResult.spec = url; parseResult.service = ary[1].toLowerCase(); var rest = ary[2]; if (rest) { ary = rest.match(/([^&#]+)/); while (ary) { rest = RegExp.rightContext.substr(1); var assignment = ary[1]; ary = assignment.match(/(.+)=(.*)/); if (ASSERT(ary, "error parsing ``" + assignment + "'' from " + url)) { var name = decodeURIComponent(ary[1]); /* only set the property the first time we see it */ if (arrayHasElementAt(ary, 2) && !(name in parseResult)) parseResult[name] = decodeURIComponent(ary[2]); } ary = rest.match(/([^&#]+)/); } } //dd (dumpObjectTree(parseResult)); return parseResult; } console.loadServiceTemplate = function con_loadservicetpl (name, sections, callback) { function onComplete (data, url, status) { if (status == Components.results.NS_OK) { var tpl = parseSections (data, sections); for (var p in sections) { if (!(p in tpl)) { display (getMsg (MSN_ERR_NO_SECTION, [sections[p], url]), MT_ERROR); callback(name, Components.results.NS_ERROR_FAILURE); return; } } console.serviceTemplates[name] = tpl; } callback(name, status); }; if (name in console.serviceTemplates) { callback(name, Components.results.NS_OK); return; } var prefName = "services." + name + ".template"; if (!(prefName in console.prefs)) { display (getMsg (MSN_ERR_NO_TEMPLATE, prefName), MT_ERROR); callback(name, Components.results.NS_ERROR_FILE_NOT_FOUND); return; } var url = console.prefs[prefName]; loadURLAsync (url, { onComplete: onComplete }); } console.asyncOpenJSDURL = asyncOpenJSDURL; function asyncOpenJSDURL (channel, streamListener, context) { function onTemplateLoaded (name, status) { if (status != Components.results.NS_OK) { response.start(); response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(url), getMsg(MSN_ERR_JSDURL_TEMPLATE, name)])); response.end(); } else { tryService(); } }; function tryService () { var serviceObject = console.services[service]; if ("requiredTemplates" in serviceObject) { for (var i = 0; i < serviceObject.requiredTemplates.length; ++i) { var def = serviceObject.requiredTemplates[i]; if (!(def[0] in console.serviceTemplates)) { console.loadServiceTemplate (def[0], def[1], onTemplateLoaded); return; } } } console.services[service](response, parseResult); }; var url = channel.URI.spec; var response = new JSDResponse (channel, streamListener, context); var parseResult = parseJSDURL (url); if (!parseResult) { response.start(); response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(url), MSG_ERR_JSDURL_PARSE])); response.end(); return; } var service = parseResult.service.toLowerCase(); if (!(service in console.services)) service = "unknown"; tryService(); } console.serviceTemplates = new Object(); console.services = new Object(); console.services["unknown"] = function svc_nosource (response, parsedURL) { response.start(); response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec), MSG_ERR_JSDURL_NOSERVICE])); response.end(); } console.services["ppbuffer"] = function svc_nosource (response) { response.start(); response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec), MSG_ERR_JSDURL_NOSOURCE])); response.end(); } console.services["help"] = function svc_help (response, parsedURL) { const CHUNK_DELAY = 100; const CHUNK_SIZE = 150; function processHelpChunk (start) { var stop = Math.min (commandList.length, start + CHUNK_SIZE); for (var i = start; i < stop; ++i) { command = commandList[i]; if (!("htmlHelp" in command)) { function replaceBold (str, p1) { return "" + p1 + ""; }; function replaceParam (str, p1) { return "<" + p1 + ">"; }; function replaceCommand (str, p1) { if (p1.indexOf(" ") != -1) { var ary = p1.split (" "); for (var i = 0; i < ary.length; ++i) { ary[i] = replaceCommand (null, ary[i]); } return ary.join (" "); } if (p1 != command.name && (p1 in console.commandManager.commands)) { return ("" + p1 + ""); } return "" + p1 + ""; }; var htmlUsage = command.usage.replace(/<([^\s>]+)>/g, replaceParam); var htmlDesc = command.help.replace(/<([^\s>]+)>/g, replaceParam); htmlDesc = htmlDesc.replace (/\*([^\*]+)\*/g, replaceBold); htmlDesc = htmlDesc.replace (/\|([^\|]+)\|/g, replaceCommand); // remove trailing access key (non en-US locales) and ... var trimmedLabel = command.labelstr.replace(/(\([a-zA-Z]\))?(\.\.\.)?$/, ""); vars = { "\\$command-name": command.name, "\\$ui-label-safe": encodeURIComponent(trimmedLabel), "\\$ui-label": fromUnicode(command.labelstr, MSG_REPORT_CHARSET), "\\$params": fromUnicode(htmlUsage, MSG_REPORT_CHARSET), "\\$key": command.keystr, "\\$desc": fromUnicode(htmlDesc, MSG_REPORT_CHARSET) }; command.htmlHelp = replaceStrings (section, vars); } response.append(command.htmlHelp); } if (i != commandList.length) { setTimeout (processHelpChunk, CHUNK_DELAY, i); } else { response.append(tpl["footer"]); response.end(); } }; function compare (a, b) { if (parsedURL.within & WITHIN_LABEL) { a = a.labelstr.toLowerCase(); b = b.labelstr.toLowerCase(); } else { a = a.name; b = b.name; } if (a == b) return 0; if (a > b) return 1; return -1; }; var command; var commandList = new Array(); var hasSearched; var tpl = console.serviceTemplates["help"]; var WITHIN_NAME = 0x01; var WITHIN_LABEL = 0x02; var WITHIN_DESC = 0x04; if ("search" in parsedURL) { try { parsedURL.search = new RegExp (parsedURL.search, "i"); } catch (ex) { response.start(); response.append(getMsg(MSN_JSDURL_ERRPAGE, [parsedURL.spec, MSG_ERR_JSDURL_SEARCH])); response.end(); return; } dd ("searching for " + parsedURL.search); if (!("within" in parsedURL) || !((parsedURL.within = parseInt(parsedURL.within)) & 0x07)) { parsedURL.within = WITHIN_NAME; } for (var c in console.commandManager.commands) { command = console.commandManager.commands[c]; if ((parsedURL.within & WITHIN_NAME) && command.name.search(parsedURL.search) != -1) { commandList.push (command); } else if ((parsedURL.within & WITHIN_LABEL) && command.labelstr.search(parsedURL.search) != -1) { commandList.push (command); } else if ((parsedURL.within & WITHIN_DESC) && command.help.search(parsedURL.search) != -1) { commandList.push (command); } } hasSearched = commandList.length > 0 ? "true" : "false"; } else { commandList = console.commandManager.list ("", CMD_CONSOLE); hasSearched = "false"; } commandList.sort(compare); response.start(); var vars = { "\\$css": console.prefs["services.help.css"], "\\$match-count": commandList.length, "\\$has-searched": hasSearched, "\\$report-charset": MSG_REPORT_CHARSET }; response.append(replaceStrings(tpl["header"], vars)); if (commandList.length == 0) { response.append(tpl["nomatch"]); response.append(tpl["footer"]); response.end(); } else { var section = tpl["command"]; processHelpChunk(0) } } console.services["help"].requiredTemplates = [ ["help", { "header" : /@-header-end/mi, "command" : /@-command-end/mi, "nomatch" : /@-nomatch-end/mi, "footer" : 0 } ] ]; const OTHER = 0; const COMMENT = 1; const STRING1 = 2; const STRING2 = 3; const WORD = 4; const NUMBER = 5; var keywords = { "abstract": 1, "boolean": 1, "break": 1, "byte": 1, "case": 1, "catch": 1, "char": 1, "class": 1, "const": 1, "continue": 1, "debugger": 1, "default": 1, "delete": 1, "do": 1, "double": 1, "else": 1, "enum": 1, "export": 1, "export": 1, "extends": 1, "false": 1, "final": 1, "finally": 1, "float": 1, "for": 1, "function": 1, "goto": 1, "if": 1, "implements": 1, "import": 1, "in": 1, "instanceof": 1, "int": 1, "interface": 1, "long": 1, "native": 1, "new": 1, "null": 1, "package": 1, "private": 1, "protected": 1, "public": 1, "return": 1, "short": 1, "static": 1, "switch": 1, "synchronized": 1, "this": 1, "throw": 1, "throws": 1, "transient": 1, "true": 1, "try": 1, "typeof": 1, "var": 1, "void": 1, "while": 1, "with": 1 }; var specialChars = /[&<>]/g; var wordStart = /[\w\\\$]/; var numberStart = /[\d]/; var otherEnd = /[\w\$\"\']|\\|\/\/|\/\*/; var wordEnd = /[^\w\$]/; var string1End = /\'/; var string2End = /\"/; var commentEnd = /\*\//; var numberEnd = /[^\d\.]/; function escapeSpecial (p1) { switch (p1) { case "&": return "&"; case "<": return "<"; case ">": return ">"; } return p1; }; function escapeSourceLine (line) { return { line: line.replace (specialChars, escapeSpecial), previousState: 0 }; } function colorizeSourceLine (line, previousState) { function closePhrase (phrase) { if (!phrase) { previousState = OTHER; return; } switch (previousState) { case COMMENT: result += "" + phrase.replace (specialChars, escapeSpecial) + ""; break; case STRING1: case STRING2: result += "" + phrase.replace (specialChars, escapeSpecial) + ""; break; case WORD: if (phrase in keywords) result += "" + phrase + ""; else result += phrase.replace (specialChars, escapeSpecial); break; case OTHER: phrase = phrase.replace (specialChars, escapeSpecial); /* fall through */ case NUMBER: result += phrase; break; } }; var result = ""; var pos; var ch, ch2; var expr; while (line.length > 0) { /* scan a line of text. |pos| always one *past* the end of the * phrase we just scanned. */ switch (previousState) { case OTHER: /* look for the end of an uncalssified token, like whitespace * or an operator. */ pos = line.search (otherEnd); break; case WORD: /* look for the end of something that qualifies as * an identifier. */ pos = line.search(wordEnd); while (pos > -1 && line[pos] == "\\") { /* if we ended with a \ character, then the slash * and the character after it are part of this word. * the characters following may also be part of the * word. */ pos += 2; var newPos = line.substr(pos).search(wordEnd); if (newPos > -1) pos += newPos; } break; case STRING1: case STRING2: /* look for the end of a single or double quoted string. */ if (previousState == STRING1) { ch = "'"; expr = string1End; } else { ch = "\""; expr = string2End; } if (line[0] == ch) { pos = 1; } else { pos = line.search (expr); if (pos > 0 && line[pos - 1] == "\\") { /* arg, the quote we found was escaped, fall back * to scanning a character at a time. */ var done = false; for (pos = 0; !done && pos < line.length; ++pos) { if (line[pos] == "\\") ++pos; else if (line[pos] == ch) done = true; } } else { if (pos != -1) ++pos; } } break; case COMMENT: /* look for the end of a slash-star comment, * slash-slash comments are handled down below, because * we know for sure that it's the last phrase on this line. */ pos = line.search (commentEnd); if (pos != -1) pos += 2; break; case NUMBER: /* look for the end of a number */ pos = line.search (numberEnd); break; } if (pos == -1) { /* couldn't find an end for the current state, close out the * rest of the line. */ closePhrase(line); line = ""; } else { /* pos has a non -1 value, close out what we found, and move * along. */ if (previousState == STRING1 || previousState == STRING2) { /* strings are a special case because they actually are * considered to start *after* the leading quote, * and they end *before* the trailing quote. */ if (pos == 1) { /* empty string */ previousState = OTHER; } else { /* non-empty string, close out the contents of the * string. */ closePhrase(line.substr (0, pos - 1)); previousState = OTHER; } /* close the trailing quote. */ result += line[pos - 1]; } else { /* non-string phrase, close the whole deal. */ closePhrase(line.substr (0, pos)); previousState = OTHER; } if (pos) line = line.substr (pos); } if (line) { /* figure out what the next token looks like. */ ch = line[0]; ch2 = (line.length > 1) ? line[1] : ""; if (ch.search (wordStart) == 0) { previousState = WORD; } else if (ch == "'") { result += "'"; line = line.substr(1); previousState = STRING1; } else if (ch == "\"") { result += "\""; line = line.substr(1); previousState = STRING2; } else if (ch == "/" && ch2 == "*") { previousState = COMMENT; } else if (ch == "/" && ch2 == "/") { /* slash-slash comment, the last thing on this line. */ previousState = COMMENT; closePhrase(line); previousState = OTHER; line = ""; } else if (ch.search (numberStart) == 0) { previousState = NUMBER; } } } return { previousState: previousState, line: result }; } console.respondWithSourceText = function con_respondsourcetext (response, sourceText) { const CHUNK_DELAY = 50; const CHUNK_SIZE = 250; var sourceLines = sourceText.lines; var resultSource; var tenSpaces = " "; var maxDigits; var previousState = 0; var mungeLine; if (console.prefs["services.source.colorize"] && sourceLines.length <= console.prefs["services.source.colorizeLimit"]) { mungeLine = colorizeSourceLine; } else { mungeLine = escapeSourceLine; } function processSourceChunk (start) { dd ("processSourceChunk " + start + " {"); var stop = Math.min (sourceLines.length, start + CHUNK_SIZE); for (var i = start; i < stop; ++i) { var padding; if (i != 999) { padding = tenSpaces.substr(0, maxDigits - Math.floor(Math.log(i + 1) / Math.LN10)); } else { /* at exactly 1000, a rounding error gets us. */ padding = tenSpaces.substr(0, maxDigits - 3); } var isExecutable; var marginContent; if ("lineMap" in sourceText && i in sourceText.lineMap) { if (sourceText.lineMap[i] & LINE_BREAKABLE) { isExecutable = "t"; marginContent = " - "; } else { isExecutable = "f"; marginContent = " "; } } else { isExecutable = "f"; marginContent = " "; } var o = mungeLine(sourceLines[i], previousState); var line = o.line; previousState = o.previousState; resultSource += "" + marginContent + "" + "" + padding + (i + 1) + " " + line + "\n"; } if (i != sourceLines.length) { setTimeout (processSourceChunk, CHUNK_DELAY, i); } else { resultSource += ""; //resultSource += ""; response.append(resultSource); response.end(); sourceText.markup = resultSource; dd ("}"); } dd ("}"); }; if ("charset" in sourceText) response.channel.contentCharset = sourceText.charset; if ("markup" in sourceText) { response.channel.contentType = "application/xml"; response.start(); response.append(sourceText.markup); response.end(); } else { maxDigits = Math.floor(Math.log(sourceLines.length) / Math.LN10) + 1; dd ("OFF building response {"); response.channel.contentType = "application/xml"; resultSource = "\n" + "\n" + "\n"; /* resultSource = "\n" + "\n" + "\n"; */ response.start(); processSourceChunk (0); } } console.services["pprint"] = function svc_pprint (response, parsedURL) { var err; if (!("scriptWrapper" in parsedURL)) { err = getMsg(MSN_ERR_REQUIRED_PARAM, "scriptWrapper"); response.start(); response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec), err])); response.end(); return; } if (!(parsedURL.scriptWrapper in console.scriptWrappers)) { err = getMsg(MSN_ERR_INVALID_PARAM, ["scriptWrapper", parsedURL.scriptWrapper]); response.start(); response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec), err])); response.end(); return; } var sourceText = console.scriptWrappers[parsedURL.scriptWrapper].sourceText; console.respondWithSourceText (response, sourceText); } console.services["source"] = function svc_source (response, parsedURL) { function onSourceTextLoaded (status) { if (status != Components.results.NS_OK) { response.start(); response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec), status])); response.end(); display (getMsg (MSN_ERR_SOURCE_LOAD_FAILED, [parsedURL.spec, status]), MT_ERROR); return; } console.respondWithSourceText (response, sourceText); } if (!("location" in parsedURL) || !parsedURL.location) { var err = getMsg(MSN_ERR_REQUIRED_PARAM, "location"); response.start(); response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec), err])); response.end(); return; } var sourceText; var targetURL = parsedURL.location; if (targetURL in console.scriptManagers) { var scriptManager = console.scriptManagers[targetURL]; if ("instance" in parsedURL) { var instance = scriptManager.getInstanceBySequence(parsedURL.instance); if (instance) sourceText = instance.sourceText; } if (!sourceText) sourceText = scriptManager.sourceText; } else { if (targetURL in console.files) sourceText = console.files[targetURL]; else sourceText = console.files[targetURL] = new SourceText (targetURL); } if (!sourceText) { response.start(); response.append(getMsg(MSN_JSDURL_ERRPAGE, [safeHTML(parsedURL.spec), MSG_ERR_JSDURL_SOURCETEXT])); response.end(); return; } if (!sourceText.isLoaded) sourceText.loadSource (onSourceTextLoaded); else onSourceTextLoaded(Components.results.NS_OK); } function JSDResponse (channel, streamListener, context) { this.hasStarted = false; this.hasEnded = false; this.channel = channel; this.streamListener = streamListener; this.context = context; } JSDResponse.prototype.start = function jsdr_start() { if (!ASSERT(!this.hasStarted, "response already started")) return; this.streamListener.onStartRequest (this.channel, this.context); this.hasStarted = true; } JSDResponse.prototype.append = function jsdr_append (str) { //dd ("appending\n" + str); const STRING_STREAM_CTRID = "@mozilla.org/io/string-input-stream;1"; const nsIStringInputStream = Components.interfaces.nsIStringInputStream; const I_LOVE_NECKO = 2152398850; var clazz = Components.classes[STRING_STREAM_CTRID]; var stringStream = clazz.createInstance(nsIStringInputStream); var len = str.length; stringStream.setData (str, len); try { this.streamListener.onDataAvailable (this.channel, this.context, stringStream, 0, len); } catch (ex) { if ("result" in ex && ex.result == I_LOVE_NECKO) { /* ignore this exception, it means the caller doesn't want the * data, or something. */ } else { throw ex; } } } JSDResponse.prototype.end = function jsdr_end () { if (!ASSERT(this.hasStarted, "response hasn't started")) return; if (!ASSERT(!this.hasEnded, "response has already ended")) return; var ok = Components.results.NS_OK; this.streamListener.onStopRequest (this.channel, this.context, ok); if (this.channel.loadGroup) this.channel.loadGroup.removeRequest (this.channel, null, ok); else dd ("channel had no load group"); this.channel._isPending = false; this.hasEnded = true; //dd ("response ended"); }