mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 20:47:44 +00:00
568 lines
16 KiB
JavaScript
568 lines
16 KiB
JavaScript
/* ***** 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 GCLI.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* The Mozilla Foundation
|
|
* Portions created by the Initial Developer are Copyright (C) 2011
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Joe Walker <jwalker@mozilla.com> (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 ***** */
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*********************************** WARNING ***********************************
|
|
*
|
|
* Do not edit this file without understanding where it comes from,
|
|
* Your changes are likely to be overwritten without warning.
|
|
*
|
|
* The original source for this file is:
|
|
* https://github.com/mozilla/gcli/
|
|
*
|
|
*******************************************************************************
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* This build of GCLI for Firefox is really 4 bits of code:
|
|
* - Browser support code - Currently just an implementation of the console
|
|
* object that uses dump. We may need to add other browser shims to this.
|
|
* - A very basic commonjs AMD (Asynchronous Modules Definition) 'require'
|
|
* implementation (which is just good enough to load GCLI). For more, see
|
|
* http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition.
|
|
* This alleviates the need for requirejs (http://requirejs.org/) which is
|
|
* used when running in the browser.
|
|
* This section of code is a copy of mini_require.js without the header and
|
|
* footers. Changes to one should be reflected in the other.
|
|
* - A build of GCLI itself, packaged using dryice (for more details see the
|
|
* project https://github.com/mozilla/dryice and the build file in this
|
|
* project at Makefile.dryice.js)
|
|
* - Lastly, code to require the gcli object as needed by EXPORTED_SYMBOLS.
|
|
*/
|
|
|
|
var EXPORTED_SYMBOLS = [ "gcli" ];
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* This creates a console object that somewhat replicates Firebug's console
|
|
* object. It currently writes to dump(), but should write to the web
|
|
* console's chrome error section (when it has one)
|
|
*/
|
|
|
|
|
|
/**
|
|
* String utility to ensure that strings are a specified length. Strings
|
|
* that are too long are truncated to the max length and the last char is
|
|
* set to "_". Strings that are too short are left padded with spaces.
|
|
*
|
|
* @param {string} aStr
|
|
* The string to format to the correct length
|
|
* @param {number} aMaxLen
|
|
* The maximum allowed length of the returned string
|
|
* @param {number} aMinLen (optional)
|
|
* The minimum allowed length of the returned string. If undefined,
|
|
* then aMaxLen will be used
|
|
* @param {object} aOptions (optional)
|
|
* An object allowing format customization. The only customization
|
|
* allowed currently is 'truncate' which can take the value "start" to
|
|
* truncate strings from the start as opposed to the end.
|
|
* @return {string}
|
|
* The original string formatted to fit the specified lengths
|
|
*/
|
|
function fmt(aStr, aMaxLen, aMinLen, aOptions) {
|
|
if (aMinLen == undefined) {
|
|
aMinLen = aMaxLen;
|
|
}
|
|
if (aStr == null) {
|
|
aStr = "";
|
|
}
|
|
if (aStr.length > aMaxLen) {
|
|
if (aOptions && aOptions.truncate == "start") {
|
|
return "_" + aStr.substring(aStr.length - aMaxLen + 1);
|
|
}
|
|
else {
|
|
return aStr.substring(0, aMaxLen - 1) + "_";
|
|
}
|
|
}
|
|
if (aStr.length < aMinLen) {
|
|
return Array(aMinLen - aStr.length + 1).join(" ") + aStr;
|
|
}
|
|
return aStr;
|
|
}
|
|
|
|
/**
|
|
* Utility to extract the constructor name of an object.
|
|
* Object.toString gives: "[object ?????]"; we want the "?????".
|
|
*
|
|
* @param {object} aObj
|
|
* The object from which to extract the constructor name
|
|
* @return {string}
|
|
* The constructor name
|
|
*/
|
|
function getCtorName(aObj) {
|
|
return Object.prototype.toString.call(aObj).slice(8, -1);
|
|
}
|
|
|
|
/**
|
|
* A single line stringification of an object designed for use by humans
|
|
*
|
|
* @param {any} aThing
|
|
* The object to be stringified
|
|
* @return {string}
|
|
* A single line representation of aThing, which will generally be at
|
|
* most 60 chars long
|
|
*/
|
|
function stringify(aThing) {
|
|
if (aThing === undefined) {
|
|
return "undefined";
|
|
}
|
|
|
|
if (aThing === null) {
|
|
return "null";
|
|
}
|
|
|
|
if (typeof aThing == "object") {
|
|
try {
|
|
return getCtorName(aThing) + " " + fmt(JSON.stringify(aThing), 50, 0);
|
|
}
|
|
catch (ex) {
|
|
return "[stringify error]";
|
|
}
|
|
}
|
|
|
|
var str = aThing.toString().replace(/\s+/g, " ");
|
|
return fmt(str, 60, 0);
|
|
}
|
|
|
|
/**
|
|
* A multi line stringification of an object, designed for use by humans
|
|
*
|
|
* @param {any} aThing
|
|
* The object to be stringified
|
|
* @return {string}
|
|
* A multi line representation of aThing
|
|
*/
|
|
function log(aThing) {
|
|
if (aThing == null) {
|
|
return "null";
|
|
}
|
|
|
|
if (aThing == undefined) {
|
|
return "undefined";
|
|
}
|
|
|
|
if (typeof aThing == "object") {
|
|
var reply = "";
|
|
var type = getCtorName(aThing);
|
|
if (type == "Error") {
|
|
reply += " " + aThing.message + "\n";
|
|
reply += logProperty("stack", aThing.stack);
|
|
}
|
|
else {
|
|
var keys = Object.getOwnPropertyNames(aThing);
|
|
if (keys.length > 0) {
|
|
reply += type + "\n";
|
|
keys.forEach(function(aProp) {
|
|
reply += logProperty(aProp, aThing[aProp]);
|
|
}, this);
|
|
}
|
|
else {
|
|
reply += type + " (enumerated with for-in)\n";
|
|
var prop;
|
|
for (prop in aThing) {
|
|
reply += logProperty(prop, aThing[prop]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
return " " + aThing.toString() + "\n";
|
|
}
|
|
|
|
/**
|
|
* Helper for log() which converts a property/value pair into an output
|
|
* string
|
|
*
|
|
* @param {string} aProp
|
|
* The name of the property to include in the output string
|
|
* @param {object} aValue
|
|
* Value assigned to aProp to be converted to a single line string
|
|
* @return {string}
|
|
* Multi line output string describing the property/value pair
|
|
*/
|
|
function logProperty(aProp, aValue) {
|
|
var reply = "";
|
|
if (aProp == "stack" && typeof value == "string") {
|
|
var trace = parseStack(aValue);
|
|
reply += formatTrace(trace);
|
|
}
|
|
else {
|
|
reply += " - " + aProp + " = " + stringify(aValue) + "\n";
|
|
}
|
|
return reply;
|
|
}
|
|
|
|
/**
|
|
* Parse a stack trace, returning an array of stack frame objects, where
|
|
* each has file/line/call members
|
|
*
|
|
* @param {string} aStack
|
|
* The serialized stack trace
|
|
* @return {object[]}
|
|
* Array of { file: "...", line: NNN, call: "..." } objects
|
|
*/
|
|
function parseStack(aStack) {
|
|
var trace = [];
|
|
aStack.split("\n").forEach(function(line) {
|
|
if (!line) {
|
|
return;
|
|
}
|
|
var at = line.lastIndexOf("@");
|
|
var posn = line.substring(at + 1);
|
|
trace.push({
|
|
file: posn.split(":")[0],
|
|
line: posn.split(":")[1],
|
|
call: line.substring(0, at)
|
|
});
|
|
}, this);
|
|
return trace;
|
|
}
|
|
|
|
/**
|
|
* parseStack() takes output from an exception from which it creates the an
|
|
* array of stack frame objects, this has the same output but using data from
|
|
* Components.stack
|
|
*
|
|
* @param {string} aFrame
|
|
* The stack frame from which to begin the walk
|
|
* @return {object[]}
|
|
* Array of { file: "...", line: NNN, call: "..." } objects
|
|
*/
|
|
function getStack(aFrame) {
|
|
if (!aFrame) {
|
|
aFrame = Components.stack.caller;
|
|
}
|
|
var trace = [];
|
|
while (aFrame) {
|
|
trace.push({
|
|
file: aFrame.filename,
|
|
line: aFrame.lineNumber,
|
|
call: aFrame.name
|
|
});
|
|
aFrame = aFrame.caller;
|
|
}
|
|
return trace;
|
|
};
|
|
|
|
/**
|
|
* Take the output from parseStack() and convert it to nice readable
|
|
* output
|
|
*
|
|
* @param {object[]} aTrace
|
|
* Array of trace objects as created by parseStack()
|
|
* @return {string} Multi line report of the stack trace
|
|
*/
|
|
function formatTrace(aTrace) {
|
|
var reply = "";
|
|
aTrace.forEach(function(frame) {
|
|
reply += fmt(frame.file, 20, 20, { truncate: "start" }) + " " +
|
|
fmt(frame.line, 5, 5) + " " +
|
|
fmt(frame.call, 75, 75) + "\n";
|
|
});
|
|
return reply;
|
|
}
|
|
|
|
/**
|
|
* Create a function which will output a concise level of output when used
|
|
* as a logging function
|
|
*
|
|
* @param {string} aLevel
|
|
* A prefix to all output generated from this function detailing the
|
|
* level at which output occurred
|
|
* @return {function}
|
|
* A logging function
|
|
* @see createMultiLineDumper()
|
|
*/
|
|
function createDumper(aLevel) {
|
|
return function() {
|
|
var args = Array.prototype.slice.call(arguments, 0);
|
|
var data = args.map(function(arg) {
|
|
return stringify(arg);
|
|
});
|
|
dump(aLevel + ": " + data.join(", ") + "\n");
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a function which will output more detailed level of output when
|
|
* used as a logging function
|
|
*
|
|
* @param {string} aLevel
|
|
* A prefix to all output generated from this function detailing the
|
|
* level at which output occurred
|
|
* @return {function}
|
|
* A logging function
|
|
* @see createDumper()
|
|
*/
|
|
function createMultiLineDumper(aLevel) {
|
|
return function() {
|
|
dump(aLevel + "\n");
|
|
var args = Array.prototype.slice.call(arguments, 0);
|
|
args.forEach(function(arg) {
|
|
dump(log(arg));
|
|
});
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The console object to expose
|
|
*/
|
|
var console = {
|
|
debug: createMultiLineDumper("debug"),
|
|
log: createDumper("log"),
|
|
info: createDumper("info"),
|
|
warn: createDumper("warn"),
|
|
error: createMultiLineDumper("error"),
|
|
trace: function Console_trace() {
|
|
var trace = getStack(Components.stack.caller);
|
|
dump(formatTrace(trace) + "\n");
|
|
},
|
|
clear: function Console_clear() {},
|
|
|
|
dir: createMultiLineDumper("dir"),
|
|
dirxml: createMultiLineDumper("dirxml"),
|
|
group: createDumper("group"),
|
|
groupEnd: createDumper("groupEnd")
|
|
};
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// There are 2 virtually identical copies of this code:
|
|
// - $GCLI_HOME/build/prefix-gcli.jsm
|
|
// - $GCLI_HOME/build/mini_require.js
|
|
// They should both be kept in sync
|
|
|
|
var debugDependencies = false;
|
|
|
|
/**
|
|
* Define a module along with a payload.
|
|
* @param {string} moduleName Name for the payload
|
|
* @param {ignored} deps Ignored. For compatibility with CommonJS AMD Spec
|
|
* @param {function} payload Function with (require, exports, module) params
|
|
*/
|
|
function define(moduleName, deps, payload) {
|
|
if (typeof moduleName != "string") {
|
|
console.error(this.depth + " Error: Module name is not a string.");
|
|
console.trace();
|
|
return;
|
|
}
|
|
|
|
if (arguments.length == 2) {
|
|
payload = deps;
|
|
}
|
|
|
|
if (debugDependencies) {
|
|
console.log("define: " + moduleName + " -> " + payload.toString()
|
|
.slice(0, 40).replace(/\n/, '\\n').replace(/\r/, '\\r') + "...");
|
|
}
|
|
|
|
if (moduleName in define.modules) {
|
|
console.error(this.depth + " Error: Redefining module: " + moduleName);
|
|
}
|
|
define.modules[moduleName] = payload;
|
|
};
|
|
|
|
/**
|
|
* The global store of un-instantiated modules
|
|
*/
|
|
define.modules = {};
|
|
|
|
|
|
/**
|
|
* We invoke require() in the context of a Domain so we can have multiple
|
|
* sets of modules running separate from each other.
|
|
* This contrasts with JSMs which are singletons, Domains allows us to
|
|
* optionally load a CommonJS module twice with separate data each time.
|
|
* Perhaps you want 2 command lines with a different set of commands in each,
|
|
* for example.
|
|
*/
|
|
function Domain() {
|
|
this.modules = {};
|
|
|
|
if (debugDependencies) {
|
|
this.depth = "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Lookup module names and resolve them by calling the definition function if
|
|
* needed.
|
|
* There are 2 ways to call this, either with an array of dependencies and a
|
|
* callback to call when the dependencies are found (which can happen
|
|
* asynchronously in an in-page context) or with a single string an no callback
|
|
* where the dependency is resolved synchronously and returned.
|
|
* The API is designed to be compatible with the CommonJS AMD spec and
|
|
* RequireJS.
|
|
* @param {string[]|string} deps A name, or names for the payload
|
|
* @param {function|undefined} callback Function to call when the dependencies
|
|
* are resolved
|
|
* @return {undefined|object} The module required or undefined for
|
|
* array/callback method
|
|
*/
|
|
Domain.prototype.require = function(deps, callback) {
|
|
if (Array.isArray(deps)) {
|
|
var params = deps.map(function(dep) {
|
|
return this.lookup(dep);
|
|
}, this);
|
|
if (callback) {
|
|
callback.apply(null, params);
|
|
}
|
|
return undefined;
|
|
}
|
|
else {
|
|
return this.lookup(deps);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Lookup module names and resolve them by calling the definition function if
|
|
* needed.
|
|
* @param {string} moduleName A name for the payload to lookup
|
|
* @return {object} The module specified by aModuleName or null if not found.
|
|
*/
|
|
Domain.prototype.lookup = function(moduleName) {
|
|
if (moduleName in this.modules) {
|
|
var module = this.modules[moduleName];
|
|
if (debugDependencies) {
|
|
console.log(this.depth + " Using module: " + moduleName);
|
|
}
|
|
return module;
|
|
}
|
|
|
|
if (!(moduleName in define.modules)) {
|
|
console.error(this.depth + " Missing module: " + moduleName);
|
|
return null;
|
|
}
|
|
|
|
var module = define.modules[moduleName];
|
|
|
|
if (debugDependencies) {
|
|
console.log(this.depth + " Compiling module: " + moduleName);
|
|
}
|
|
|
|
if (typeof module == "function") {
|
|
if (debugDependencies) {
|
|
this.depth += ".";
|
|
}
|
|
|
|
var exports = {};
|
|
try {
|
|
module(this.require.bind(this), exports, { id: moduleName, uri: "" });
|
|
}
|
|
catch (ex) {
|
|
console.error("Error using module: " + moduleName, ex);
|
|
throw ex;
|
|
}
|
|
module = exports;
|
|
|
|
if (debugDependencies) {
|
|
this.depth = this.depth.slice(0, -1);
|
|
}
|
|
}
|
|
|
|
// cache the resulting module object for next time
|
|
this.modules[moduleName] = module;
|
|
|
|
return module;
|
|
};
|
|
|
|
/**
|
|
* Expose the Domain constructor and a global domain (on the define function
|
|
* to avoid exporting more than we need. This is a common pattern with require
|
|
* systems)
|
|
*/
|
|
define.Domain = Domain;
|
|
define.globalDomain = new Domain();
|
|
|
|
/**
|
|
* Expose a default require function which is the require of the global
|
|
* sandbox to make it easy to use.
|
|
*/
|
|
var require = define.globalDomain.require.bind(define.globalDomain);
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* The API of interest to people wanting to create GCLI commands is as
|
|
* follows. The implementation of this API is left to bug 659061 and other
|
|
* bugs.
|
|
*/
|
|
|
|
define('gcli/index', [ ], function(require, exports, module) {
|
|
|
|
exports.addCommand = function() { /* implementation goes here */ };
|
|
exports.removeCommand = function() { /* implementation goes here */ };
|
|
exports.startup = function() { /* implementation goes here */ };
|
|
exports.shutdown = function() { /* implementation goes here */ };
|
|
|
|
});
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* require GCLI so it can be exported as declared in EXPORTED_SYMBOLS
|
|
* The dependencies specified here should be the same as in Makefile.dryice.js
|
|
*/
|
|
var gcli = require("gcli/index");
|
|
gcli.createView = require("gcli/ui/start/firefox");
|
|
gcli._internal = { require: require, define: define, console: console };
|
|
|