Bug 693269 - Ensure jstermhelpers work in JS mode in GCLI; r=dcamp,msucan

This commit is contained in:
Joe Walker 2012-01-27 18:24:57 +00:00
Родитель 257a8928f5
Коммит 3490516164
6 изменённых файлов: 281 добавлений и 62 удалений

Просмотреть файл

@ -4708,7 +4708,7 @@ function JSTermHelper(aJSTerm)
aJSTerm.sandbox.inspectrules = function JSTH_inspectrules(aNode)
{
aJSTerm.helperEvaluated = true;
let doc = aJSTerm.parentNode.ownerDocument;
let doc = aJSTerm.inputNode.ownerDocument;
let win = doc.defaultView;
let panel = createElement(doc, "panel", {
label: "CSS Rules",
@ -4822,6 +4822,7 @@ function JSTerm(aContext, aParentNode, aMixin, aConsole)
this.parentNode = aParentNode;
this.mixins = aMixin;
this.console = aConsole;
this.document = aParentNode.ownerDocument
this.setTimeout = aParentNode.ownerDocument.defaultView.setTimeout;
@ -5027,7 +5028,7 @@ JSTerm.prototype = {
});
}
let doc = self.parentNode.ownerDocument;
let doc = self.document;
let parent = doc.getElementById("mainPopupSet");
let title = (aEvalString
? HUDService.getFormatStr("jsPropertyInspectTitle", [aEvalString])
@ -5056,7 +5057,7 @@ JSTerm.prototype = {
*/
writeOutputJS: function JST_writeOutputJS(aEvalString, aOutputObject, aOutputString)
{
let node = ConsoleUtils.createMessageNode(this.parentNode.ownerDocument,
let node = ConsoleUtils.createMessageNode(this.document,
CATEGORY_OUTPUT,
SEVERITY_LOG,
aOutputString,
@ -5105,7 +5106,7 @@ JSTerm.prototype = {
*/
writeOutput: function JST_writeOutput(aOutputMessage, aCategory, aSeverity)
{
let node = ConsoleUtils.createMessageNode(this.parentNode.ownerDocument,
let node = ConsoleUtils.createMessageNode(this.document,
aCategory, aSeverity,
aOutputMessage, this.hudId);
@ -6828,6 +6829,7 @@ function GcliTerm(aContentWindow, aHudId, aDocument, aConsole, aHintNode, aConso
this.document = aDocument;
this.console = aConsole;
this.hintNode = aHintNode;
this._window = this.context.get().QueryInterface(Ci.nsIDOMWindow);
this.createUI();
this.createSandbox();
@ -6924,6 +6926,7 @@ GcliTerm.prototype = {
delete this.document;
delete this.console;
delete this.hintNode;
delete this._window;
delete this.sandbox;
delete this.element
@ -6983,14 +6986,59 @@ GcliTerm.prototype = {
return;
}
this.writeOutput(aEvent.output.typed, { category: CATEGORY_INPUT });
this.writeOutput(aEvent.output.typed, CATEGORY_INPUT);
if (aEvent.output.output == null) {
// This is an experiment to see how much people yell when we stop reporting
// undefined replies.
if (aEvent.output.output === undefined) {
return;
}
let output = aEvent.output.output;
if (aEvent.output.command.returnType == "html" && typeof output == "string") {
let declaredType = aEvent.output.command.returnType || "";
if (declaredType == "object") {
let actualType = typeof output;
if (output === null) {
output = "null";
}
else if (actualType == "string") {
output = "\"" + output + "\"";
}
else if (actualType == "object" || actualType == "function") {
let formatOpts = [ nameObject(output) ];
output = stringBundle.formatStringFromName('gcliterm.instanceLabel',
formatOpts, formatOpts.length);
let linkNode = this.document.createElementNS(HTML_NS, 'html:span');
linkNode.appendChild(this.document.createTextNode(output));
linkNode.classList.add("hud-clickable");
linkNode.setAttribute("aria-haspopup", "true");
// Make the object bring up the property panel.
linkNode.addEventListener("mousedown", function(aEv) {
this._startX = aEv.clientX;
this._startY = aEv.clientY;
}.bind(this), false);
linkNode.addEventListener("click", function(aEv) {
if (aEv.detail != 1 || aEv.button != 0 ||
(this._startX != aEv.clientX && this._startY != aEv.clientY)) {
return;
}
if (!this._panelOpen) {
let propPanel = this.openPropertyPanel(aEvent.output.typed, aEvent.output.output, this);
propPanel.panel.setAttribute("hudId", this.hudId);
this._panelOpen = true;
}
}.bind(this), false);
output = linkNode;
}
// else if (actualType == number/boolean/undefined) do nothing
}
if (declaredType == "html" && typeof output == "string") {
output = this.document.createRange().createContextualFragment(
'<div xmlns="' + HTML_NS + '" xmlns:xul="' + XUL_NS + '">' +
output + '</div>');
@ -7030,14 +7078,14 @@ GcliTerm.prototype = {
*/
createSandbox: function Gcli_createSandbox()
{
let win = this.context.get().QueryInterface(Ci.nsIDOMWindow);
// create a JS Sandbox out of this.context
this.sandbox = new Cu.Sandbox(win, {
sandboxPrototype: win,
this.sandbox = new Cu.Sandbox(this._window, {
sandboxPrototype: this._window,
wantXrays: false
});
this.sandbox.console = this.console;
JSTermHelper(this);
},
/**
@ -7049,7 +7097,31 @@ GcliTerm.prototype = {
*/
evalInSandbox: function Gcli_evalInSandbox(aString)
{
return Cu.evalInSandbox(aString, this.sandbox, "1.8", "Web Console", 1);
let window = unwrap(this.sandbox.window);
let temp$ = null;
let temp$$ = null;
// We prefer to execute the page-provided implementations for the $() and
// $$() functions.
if (typeof window.$ == "function") {
temp$ = this.sandbox.$;
delete this.sandbox.$;
}
if (typeof window.$$ == "function") {
temp$$ = this.sandbox.$$;
delete this.sandbox.$$;
}
let result = Cu.evalInSandbox(aString, this.sandbox, "1.8", "Web Console", 1);
if (temp$) {
this.sandbox.$ = temp$;
}
if (temp$$) {
this.sandbox.$$ = temp$$;
}
return result;
},
/**
@ -7063,14 +7135,14 @@ GcliTerm.prototype = {
* @param number aSeverity
* One of the SEVERITY_ constants.
*/
writeOutput: function Gcli_writeOutput(aOutputMessage, aOptions)
writeOutput: function Gcli_writeOutput(aOutputMessage, aCategory, aSeverity, aOptions)
{
aOptions = aOptions || {};
let node = ConsoleUtils.createMessageNode(
this.document,
aOptions.category || CATEGORY_OUTPUT,
aOptions.severity || SEVERITY_LOG,
aCategory || CATEGORY_OUTPUT,
aSeverity || SEVERITY_LOG,
aOutputMessage,
this.hudId,
aOptions.sourceUrl || undefined,
@ -7081,8 +7153,21 @@ GcliTerm.prototype = {
},
clearOutput: JSTerm.prototype.clearOutput,
openPropertyPanel: JSTerm.prototype.openPropertyPanel,
formatResult: JSTerm.prototype.formatResult,
getResultType: JSTerm.prototype.getResultType,
formatString: JSTerm.prototype.formatString,
};
/**
* A fancy version of toString()
*/
function nameObject(aObj) {
if (aObj.constructor && aObj.constructor.name) {
return aObj.constructor.name;
}
// If that fails, use Objects toString which sometimes gives something
// better than 'Object', and at least defaults to Object if nothing better
return Object.prototype.toString.call(aObj).slice(8, -1);
}

Просмотреть файл

@ -200,6 +200,11 @@ var console = {};
* The constructor name
*/
function getCtorName(aObj) {
if (aObj.constructor && aObj.constructor.name) {
return aObj.constructor.name;
}
// If that fails, use Objects toString which sometimes gives something
// better than 'Object', and at least defaults to Object if nothing better
return Object.prototype.toString.call(aObj).slice(8, -1);
}
@ -870,6 +875,10 @@ function Command(commandSpec) {
// parameter groups.
var usingGroups = false;
if (this.returnType == null) {
this.returnType = 'string';
}
// In theory this could easily be made recursive, so param groups could
// contain nested param groups. Current thinking is that the added
// complexity for the UI probably isn't worth it, so this implementation
@ -3020,6 +3029,15 @@ JavascriptType.prototype.parse = function(arg) {
var typed = arg.text;
var scope = globalObject;
// Just accept numbers
if (!isNaN(parseFloat(typed)) && isFinite(typed)) {
return new Conversion(typed, arg);
}
// Just accept constants like true/false/null/etc
if (typed.trim().match(/(null|undefined|NaN|Infinity|true|false)/)) {
return new Conversion(typed, arg);
}
// Analyze the input text and find the beginning of the last part that
// should be completed.
var beginning = this._findCompletionBeginning(typed);
@ -3984,34 +4002,12 @@ var evalCommandSpec = {
description: ''
}
],
returnType: 'html',
returnType: 'object',
description: { key: 'cliEvalJavascript' },
exec: function(args, context) {
// &#x2192; is right arrow. We use explicit entities to ensure XML validity
var resultPrefix = '<em>{ ' + args.javascript + ' }</em> &#x2192; ';
try {
var result = customEval(args.javascript);
if (result === null) {
return resultPrefix + 'null.';
}
if (result === undefined) {
return resultPrefix + 'undefined.';
}
if (typeof result === 'function') {
// &#160; is &nbsp;
return resultPrefix +
(result + '').replace(/\n/g, '<br>').replace(/ /g, '&#160;');
}
return resultPrefix + result;
}
catch (ex) {
return resultPrefix + 'Exception: ' + ex.message;
}
}
return customEval(args.javascript);
},
evalRegexp: /^\s*{\s*/
};
@ -4195,8 +4191,9 @@ Requisition.prototype._onAssignmentChange = function(ev) {
// Refactor? See bug 660765
// Do preceding arguments need to have dummy values applied so we don't
// get a hole in the command line?
var i;
if (ev.assignment.param.isPositionalAllowed()) {
for (var i = 0; i < ev.assignment.paramIndex; i++) {
for (i = 0; i < ev.assignment.paramIndex; i++) {
var assignment = this.getAssignment(i);
if (assignment.param.isPositionalAllowed()) {
if (assignment.ensureVisibleArgument()) {
@ -4208,7 +4205,7 @@ Requisition.prototype._onAssignmentChange = function(ev) {
// Remember where we found the first match
var index = MORE_THAN_THE_MOST_ARGS_POSSIBLE;
for (var i = 0; i < this._args.length; i++) {
for (i = 0; i < this._args.length; i++) {
if (this._args[i].assignment === ev.assignment) {
if (i < index) {
index = i;
@ -4224,7 +4221,7 @@ Requisition.prototype._onAssignmentChange = function(ev) {
else {
// Is there a way to do this that doesn't involve a loop?
var newArgs = ev.conversion.arg.getArgs();
for (var i = 0; i < newArgs.length; i++) {
for (i = 0; i < newArgs.length; i++) {
this._args.splice(index + i, 0, newArgs[i]);
}
}
@ -4267,14 +4264,14 @@ Requisition.prototype.getAssignment = function(nameOrNumber) {
nameOrNumber :
Object.keys(this._assignments)[nameOrNumber];
return this._assignments[name] || undefined;
},
};
/**
* Where parameter name == assignment names - they are the same
*/
Requisition.prototype.getParameterNames = function() {
return Object.keys(this._assignments);
},
};
/**
* A *shallow* clone of the assignments.
@ -4407,14 +4404,15 @@ Requisition.prototype.createInputArgTrace = function() {
}
var args = [];
var i;
this._args.forEach(function(arg) {
for (var i = 0; i < arg.prefix.length; i++) {
for (i = 0; i < arg.prefix.length; i++) {
args.push({ arg: arg, char: arg.prefix[i], part: 'prefix' });
}
for (var i = 0; i < arg.text.length; i++) {
for (i = 0; i < arg.text.length; i++) {
args.push({ arg: arg, char: arg.text[i], part: 'text' });
}
for (var i = 0; i < arg.suffix.length; i++) {
for (i = 0; i < arg.suffix.length; i++) {
args.push({ arg: arg, char: arg.suffix[i], part: 'suffix' });
}
});
@ -4575,10 +4573,18 @@ Requisition.prototype.exec = function(input) {
return false;
}
// Display JavaScript input without the initial { or closing }
var typed = this.toString();
if (evalCommandSpec.evalRegexp.test(typed)) {
typed = typed.replace(evalCommandSpec.evalRegexp, '');
// Bug 717763: What if the JavaScript naturally ends with a }?
typed = typed.replace(/\s*}\s*$/, '');
}
var outputObject = {
command: command,
args: args,
typed: this.toString(),
typed: typed,
canonical: this.toCanonicalString(),
completed: false,
start: new Date()
@ -4586,7 +4592,7 @@ Requisition.prototype.exec = function(input) {
this.commandOutputManager.sendCommandOutput(outputObject);
var onComplete = (function(output, error) {
var onComplete = function(output, error) {
if (visible) {
outputObject.end = new Date();
outputObject.duration = outputObject.end.getTime() - outputObject.start.getTime();
@ -4595,7 +4601,7 @@ Requisition.prototype.exec = function(input) {
outputObject.completed = true;
this.commandOutputManager.sendCommandOutput(outputObject);
}
}).bind(this);
}.bind(this);
try {
var context = new ExecutionContext(this);
@ -4614,6 +4620,7 @@ Requisition.prototype.exec = function(input) {
}
}
catch (ex) {
console.error(ex);
onComplete(ex, true);
}
@ -4768,6 +4775,7 @@ Requisition.prototype._tokenize = function(typed) {
while (true) {
var c = typed[i];
var str;
switch (mode) {
case In.WHITESPACE:
if (c === '\'') {
@ -4800,7 +4808,7 @@ Requisition.prototype._tokenize = function(typed) {
// There is an edge case of xx'xx which we are assuming to
// be a single parameter (and same with ")
if (c === ' ') {
var str = unescape2(typed.substring(start, i));
str = unescape2(typed.substring(start, i));
args.push(new Argument(str, prefix, ''));
mode = In.WHITESPACE;
start = i;
@ -4810,7 +4818,7 @@ Requisition.prototype._tokenize = function(typed) {
case In.SINGLE_Q:
if (c === '\'') {
var str = unescape2(typed.substring(start, i));
str = unescape2(typed.substring(start, i));
args.push(new Argument(str, prefix, c));
mode = In.WHITESPACE;
start = i + 1;
@ -4820,7 +4828,7 @@ Requisition.prototype._tokenize = function(typed) {
case In.DOUBLE_Q:
if (c === '"') {
var str = unescape2(typed.substring(start, i));
str = unescape2(typed.substring(start, i));
args.push(new Argument(str, prefix, c));
mode = In.WHITESPACE;
start = i + 1;
@ -4835,7 +4843,7 @@ Requisition.prototype._tokenize = function(typed) {
else if (c === '}') {
blockDepth--;
if (blockDepth === 0) {
var str = unescape2(typed.substring(start, i));
str = unescape2(typed.substring(start, i));
args.push(new ScriptArgument(str, prefix, c));
mode = In.WHITESPACE;
start = i + 1;
@ -4864,11 +4872,11 @@ Requisition.prototype._tokenize = function(typed) {
}
}
else if (mode === In.SCRIPT) {
var str = unescape2(typed.substring(start, i + 1));
str = unescape2(typed.substring(start, i + 1));
args.push(new ScriptArgument(str, prefix, ''));
}
else {
var str = unescape2(typed.substring(start, i + 1));
str = unescape2(typed.substring(start, i + 1));
args.push(new Argument(str, prefix, ''));
}
break;
@ -4901,16 +4909,16 @@ Requisition.prototype._split = function(args) {
// Handle the special case of the user typing { javascript(); }
// We use the hidden 'eval' command directly rather than shift()ing one of
// the parameters, and parse()ing it.
var conversion;
if (args[0] instanceof ScriptArgument) {
// Special case: if the user enters { console.log('foo'); } then we need to
// use the hidden 'eval' command
var conversion = new Conversion(evalCommand, new Argument());
conversion = new Conversion(evalCommand, new Argument());
this.commandAssignment.setConversion(conversion);
return;
}
var argsUsed = 1;
var conversion;
while (argsUsed <= args.length) {
var arg = (argsUsed === 1) ?

Просмотреть файл

@ -144,6 +144,7 @@ _BROWSER_TEST_FILES = \
browser_webconsole_bug_664131_console_group.js \
browser_webconsole_bug_704295.js \
browser_gcli_commands.js \
browser_gcli_helpers.js \
browser_gcli_inspect.js \
browser_gcli_integrate.js \
browser_gcli_require.js \

Просмотреть файл

@ -5,8 +5,6 @@
// - https://github.com/mozilla/gcli/blob/master/docs/index.md
// - https://wiki.mozilla.org/DevTools/Features/GCLI
// Tests that the inspect command works as it should
Components.utils.import("resource:///modules/gcli.jsm");
let hud;

Просмотреть файл

@ -0,0 +1,122 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// For more information on GCLI see:
// - https://github.com/mozilla/gcli/blob/master/docs/index.md
// - https://wiki.mozilla.org/DevTools/Features/GCLI
Components.utils.import("resource:///modules/gcli.jsm");
let hud;
let gcliterm;
registerCleanupFunction(function() {
gcliterm = undefined;
hud = undefined;
Services.prefs.clearUserPref("devtools.gcli.enable");
});
function test() {
Services.prefs.setBoolPref("devtools.gcli.enable", true);
addTab("http://example.com/browser/browser/devtools/webconsole/test//test-console.html");
browser.addEventListener("DOMContentLoaded", onLoad, false);
}
function onLoad() {
browser.removeEventListener("DOMContentLoaded", onLoad, false);
openConsole();
hud = HUDService.getHudByWindow(content);
gcliterm = hud.gcliterm;
testHelpers();
testScripts();
closeConsole();
finishTest();
// gcli._internal.console.error("Command Tests Completed");
}
function testScripts() {
check("{ 'id=' + $('header').getAttribute('id')", '"id=header"');
check("{ headerQuery = $$('h1')", "Instance of NodeList");
check("{ 'length=' + headerQuery.length", '"length=1"');
check("{ xpathQuery = $x('.//*', document.body);", 'Instance of Array');
check("{ 'headerFound=' + (xpathQuery[0] == headerQuery[0])", '"headerFound=true"');
check("{ 'keysResult=' + (keys({b:1})[0] == 'b')", '"keysResult=true"');
check("{ 'valuesResult=' + (values({b:1})[0] == 1)", '"valuesResult=true"');
check("{ [] instanceof Array", "true");
check("{ ({}) instanceof Object", "true");
check("{ document", "Instance of HTMLDocument");
check("{ null", "null");
check("{ undefined", undefined);
check("{ for (var x=0; x<9; x++) x;", "8");
}
function check(command, reply) {
gcliterm.clearOutput();
gcliterm.opts.console.inputter.setInput(command);
gcliterm.opts.requisition.exec();
let labels = hud.outputNode.querySelectorAll(".webconsole-msg-output");
if (reply === undefined) {
is(labels.length, 0, "results count for: " + command);
}
else {
is(labels.length, 1, "results count for: " + command);
is(labels[0].textContent.trim(), reply, "message for: " + command);
}
gcliterm.opts.console.inputter.setInput("");
}
function testHelpers() {
gcliterm.clearOutput();
gcliterm.opts.console.inputter.setInput("{ pprint({b:2, a:1})");
gcliterm.opts.requisition.exec();
// Doesn't conform to check() format
let label = hud.outputNode.querySelector(".webconsole-msg-output");
is(label.textContent.trim(), "a: 1\n b: 2", "pprint() worked");
// no gcliterm.clearOutput() here as we clear the output using the clear() fn.
gcliterm.opts.console.inputter.setInput("{ clear()");
gcliterm.opts.requisition.exec();
ok(!hud.outputNode.querySelector(".hud-group"), "clear() worked");
// check that pprint(window) and keys(window) don't throw, bug 608358
gcliterm.clearOutput();
gcliterm.opts.console.inputter.setInput("{ pprint(window)");
gcliterm.opts.requisition.exec();
let labels = hud.outputNode.querySelectorAll(".webconsole-msg-output");
is(labels.length, 1, "one line of output for pprint(window)");
gcliterm.clearOutput();
gcliterm.opts.console.inputter.setInput("{ keys(window)");
gcliterm.opts.requisition.exec();
labels = hud.outputNode.querySelectorAll(".webconsole-msg-output");
is(labels.length, 1, "one line of output for keys(window)");
gcliterm.clearOutput();
gcliterm.opts.console.inputter.setInput("{ pprint('hi')");
gcliterm.opts.requisition.exec();
// Doesn't conform to check() format, bug 614561
label = hud.outputNode.querySelector(".webconsole-msg-output");
is(label.textContent.trim(), '0: "h"\n 1: "i"', 'pprint("hi") worked');
// Causes a memory leak. FIXME Bug 717892
/*
// check that pprint(function) shows function source, bug 618344
gcliterm.clearOutput();
gcliterm.opts.console.inputter.setInput("{ pprint(print)");
gcliterm.opts.requisition.exec();
label = hud.outputNode.querySelector(".webconsole-msg-output");
isnot(label.textContent.indexOf("SEVERITY_LOG"), -1, "pprint(function) shows function source");
*/
gcliterm.clearOutput();
}

Просмотреть файл

@ -151,6 +151,11 @@ webConsoleWindowTitleAndURL=Web Console - %S
# Javascript is being entered, to indicate how to jump into scratchpad mode
scratchpad.linkText=Shift+RETURN - Open in Scratchpad
# LOCALIZATION NOTE (gcliterm.instanceLabel): The console displays
# objects using their type (from the constructor function) in this descriptive
# string
gcliterm.instanceLabel=Instance of %S
# LOCALIZATION NOTE (Autocomplete.label):
# The autocomplete popup panel label/title.
Autocomplete.label=Autocomplete popup