зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1217591 - Make JS autocompletion in the console work inside of a worker toolbox;r=fitzgen
--HG-- rename : devtools/shared/webconsole/utils.js => devtools/shared/webconsole/js-property-provider.js extra : commitid : Am93ZaJlGjE
This commit is contained in:
Родитель
f959a4d1ef
Коммит
f2795041dc
|
@ -17,7 +17,7 @@ function test() {
|
|||
}
|
||||
|
||||
function consoleOpened(HUD) {
|
||||
let {JSPropertyProvider} = require("devtools/shared/webconsole/utils");
|
||||
let {JSPropertyProvider} = require("devtools/shared/webconsole/js-property-provider");
|
||||
|
||||
let tmp = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
|
||||
tmp.addDebuggerToGlobal(tmp);
|
||||
|
|
|
@ -16,7 +16,7 @@ function test() {
|
|||
|
||||
function testPropertyProvider({browser}) {
|
||||
browser.removeEventListener("load", testPropertyProvider, true);
|
||||
let {JSPropertyProvider} = require("devtools/shared/webconsole/utils");
|
||||
let {JSPropertyProvider} = require("devtools/shared/webconsole/js-property-provider");
|
||||
|
||||
let tmp = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
|
||||
tmp.addDebuggerToGlobal(tmp);
|
||||
|
|
|
@ -18,9 +18,10 @@ loader.lazyRequireGetter(this, "NetworkMonitorChild", "devtools/shared/webconsol
|
|||
loader.lazyRequireGetter(this, "ConsoleProgressListener", "devtools/shared/webconsole/network-monitor", true);
|
||||
loader.lazyRequireGetter(this, "events", "sdk/event/core");
|
||||
loader.lazyRequireGetter(this, "ServerLoggingListener", "devtools/shared/webconsole/server-logger", true);
|
||||
loader.lazyRequireGetter(this, "JSPropertyProvider", "devtools/shared/webconsole/js-property-provider", true);
|
||||
|
||||
for (let name of ["WebConsoleUtils", "ConsoleServiceListener",
|
||||
"ConsoleAPIListener", "addWebConsoleCommands", "JSPropertyProvider",
|
||||
"ConsoleAPIListener", "addWebConsoleCommands",
|
||||
"ConsoleReflowListener", "CONSOLE_WORKER_IDS"]) {
|
||||
Object.defineProperty(this, name, {
|
||||
get: function(prop) {
|
||||
|
|
|
@ -0,0 +1,523 @@
|
|||
/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Cc, Ci, Cu, components} = require("chrome");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
if (!isWorker) {
|
||||
loader.lazyImporter(this, "Parser", "resource://devtools/shared/Parser.jsm");
|
||||
}
|
||||
|
||||
// Provide an easy way to bail out of even attempting an autocompletion
|
||||
// if an object has way too many properties. Protects against large objects
|
||||
// with numeric values that wouldn't be tallied towards MAX_AUTOCOMPLETIONS.
|
||||
const MAX_AUTOCOMPLETE_ATTEMPTS = exports.MAX_AUTOCOMPLETE_ATTEMPTS = 100000;
|
||||
// Prevent iterating over too many properties during autocomplete suggestions.
|
||||
const MAX_AUTOCOMPLETIONS = exports.MAX_AUTOCOMPLETIONS = 1500;
|
||||
|
||||
const STATE_NORMAL = 0;
|
||||
const STATE_QUOTE = 2;
|
||||
const STATE_DQUOTE = 3;
|
||||
|
||||
const OPEN_BODY = "{[(".split("");
|
||||
const CLOSE_BODY = "}])".split("");
|
||||
const OPEN_CLOSE_BODY = {
|
||||
"{": "}",
|
||||
"[": "]",
|
||||
"(": ")",
|
||||
};
|
||||
|
||||
/**
|
||||
* Analyses a given string to find the last statement that is interesting for
|
||||
* later completion.
|
||||
*
|
||||
* @param string aStr
|
||||
* A string to analyse.
|
||||
*
|
||||
* @returns object
|
||||
* If there was an error in the string detected, then a object like
|
||||
*
|
||||
* { err: "ErrorMesssage" }
|
||||
*
|
||||
* is returned, otherwise a object like
|
||||
*
|
||||
* {
|
||||
* state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
|
||||
* startPos: index of where the last statement begins
|
||||
* }
|
||||
*/
|
||||
function findCompletionBeginning(aStr)
|
||||
{
|
||||
let bodyStack = [];
|
||||
|
||||
let state = STATE_NORMAL;
|
||||
let start = 0;
|
||||
let c;
|
||||
for (let i = 0; i < aStr.length; i++) {
|
||||
c = aStr[i];
|
||||
|
||||
switch (state) {
|
||||
// Normal JS state.
|
||||
case STATE_NORMAL:
|
||||
if (c == '"') {
|
||||
state = STATE_DQUOTE;
|
||||
}
|
||||
else if (c == "'") {
|
||||
state = STATE_QUOTE;
|
||||
}
|
||||
else if (c == ";") {
|
||||
start = i + 1;
|
||||
}
|
||||
else if (c == " ") {
|
||||
start = i + 1;
|
||||
}
|
||||
else if (OPEN_BODY.indexOf(c) != -1) {
|
||||
bodyStack.push({
|
||||
token: c,
|
||||
start: start
|
||||
});
|
||||
start = i + 1;
|
||||
}
|
||||
else if (CLOSE_BODY.indexOf(c) != -1) {
|
||||
var last = bodyStack.pop();
|
||||
if (!last || OPEN_CLOSE_BODY[last.token] != c) {
|
||||
return {
|
||||
err: "syntax error"
|
||||
};
|
||||
}
|
||||
if (c == "}") {
|
||||
start = i + 1;
|
||||
}
|
||||
else {
|
||||
start = last.start;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Double quote state > " <
|
||||
case STATE_DQUOTE:
|
||||
if (c == "\\") {
|
||||
i++;
|
||||
}
|
||||
else if (c == "\n") {
|
||||
return {
|
||||
err: "unterminated string literal"
|
||||
};
|
||||
}
|
||||
else if (c == '"') {
|
||||
state = STATE_NORMAL;
|
||||
}
|
||||
break;
|
||||
|
||||
// Single quote state > ' <
|
||||
case STATE_QUOTE:
|
||||
if (c == "\\") {
|
||||
i++;
|
||||
}
|
||||
else if (c == "\n") {
|
||||
return {
|
||||
err: "unterminated string literal"
|
||||
};
|
||||
}
|
||||
else if (c == "'") {
|
||||
state = STATE_NORMAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
state: state,
|
||||
startPos: start
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of properties, that are possible matches based on the passed
|
||||
* Debugger.Environment/Debugger.Object and inputValue.
|
||||
*
|
||||
* @param object aDbgObject
|
||||
* When the debugger is not paused this Debugger.Object wraps the scope for autocompletion.
|
||||
* It is null if the debugger is paused.
|
||||
* @param object anEnvironment
|
||||
* When the debugger is paused this Debugger.Environment is the scope for autocompletion.
|
||||
* It is null if the debugger is not paused.
|
||||
* @param string aInputValue
|
||||
* Value that should be completed.
|
||||
* @param number [aCursor=aInputValue.length]
|
||||
* Optional offset in the input where the cursor is located. If this is
|
||||
* omitted then the cursor is assumed to be at the end of the input
|
||||
* value.
|
||||
* @returns null or object
|
||||
* If no completion valued could be computed, null is returned,
|
||||
* otherwise a object with the following form is returned:
|
||||
* {
|
||||
* matches: [ string, string, string ],
|
||||
* matchProp: Last part of the inputValue that was used to find
|
||||
* the matches-strings.
|
||||
* }
|
||||
*/
|
||||
function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor)
|
||||
{
|
||||
if (aCursor === undefined) {
|
||||
aCursor = aInputValue.length;
|
||||
}
|
||||
|
||||
let inputValue = aInputValue.substring(0, aCursor);
|
||||
|
||||
// Analyse the inputValue and find the beginning of the last part that
|
||||
// should be completed.
|
||||
let beginning = findCompletionBeginning(inputValue);
|
||||
|
||||
// There was an error analysing the string.
|
||||
if (beginning.err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the current state is not STATE_NORMAL, then we are inside of an string
|
||||
// which means that no completion is possible.
|
||||
if (beginning.state != STATE_NORMAL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let completionPart = inputValue.substring(beginning.startPos);
|
||||
let lastDot = completionPart.lastIndexOf(".");
|
||||
|
||||
// Don't complete on just an empty string.
|
||||
if (completionPart.trim() == "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Catch literals like [1,2,3] or "foo" and return the matches from
|
||||
// their prototypes.
|
||||
// Don't run this is a worker, migrating to acorn should allow this
|
||||
// to run in a worker - Bug 1217198.
|
||||
if (!isWorker && lastDot > 0) {
|
||||
let parser = new Parser();
|
||||
parser.logExceptions = false;
|
||||
let syntaxTree = parser.get(completionPart.slice(0, lastDot));
|
||||
let lastTree = syntaxTree.getLastSyntaxTree();
|
||||
let lastBody = lastTree && lastTree.AST.body[lastTree.AST.body.length - 1];
|
||||
|
||||
// Finding the last expression since we've sliced up until the dot.
|
||||
// If there were parse errors this won't exist.
|
||||
if (lastBody) {
|
||||
let expression = lastBody.expression;
|
||||
let matchProp = completionPart.slice(lastDot + 1);
|
||||
if (expression.type === "ArrayExpression") {
|
||||
return getMatchedProps(Array.prototype, matchProp);
|
||||
} else if (expression.type === "Literal" &&
|
||||
(typeof expression.value === "string")) {
|
||||
return getMatchedProps(String.prototype, matchProp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We are completing a variable / a property lookup.
|
||||
let properties = completionPart.split(".");
|
||||
let matchProp = properties.pop().trimLeft();
|
||||
let obj = aDbgObject;
|
||||
|
||||
// The first property must be found in the environment if the debugger is
|
||||
// paused.
|
||||
if (anEnvironment) {
|
||||
if (properties.length == 0) {
|
||||
return getMatchedPropsInEnvironment(anEnvironment, matchProp);
|
||||
}
|
||||
obj = getVariableInEnvironment(anEnvironment, properties.shift());
|
||||
}
|
||||
|
||||
if (!isObjectUsable(obj)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We get the rest of the properties recursively starting from the Debugger.Object
|
||||
// that wraps the first property
|
||||
for (let prop of properties) {
|
||||
prop = prop.trim();
|
||||
if (!prop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (/\[\d+\]$/.test(prop)) {
|
||||
// The property to autocomplete is a member of array. For example
|
||||
// list[i][j]..[n]. Traverse the array to get the actual element.
|
||||
obj = getArrayMemberProperty(obj, prop);
|
||||
}
|
||||
else {
|
||||
obj = DevToolsUtils.getProperty(obj, prop);
|
||||
}
|
||||
|
||||
if (!isObjectUsable(obj)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// If the final property is a primitive
|
||||
if (typeof obj != "object") {
|
||||
return getMatchedProps(obj, matchProp);
|
||||
}
|
||||
|
||||
return getMatchedPropsInDbgObject(obj, matchProp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array member of aObj for the given aProp. For example, given
|
||||
* aProp='list[0][1]' the element at [0][1] of aObj.list is returned.
|
||||
*
|
||||
* @param object aObj
|
||||
* The object to operate on.
|
||||
* @param string aProp
|
||||
* The property to return.
|
||||
* @return null or Object
|
||||
* Returns null if the property couldn't be located. Otherwise the array
|
||||
* member identified by aProp.
|
||||
*/
|
||||
function getArrayMemberProperty(aObj, aProp)
|
||||
{
|
||||
// First get the array.
|
||||
let obj = aObj;
|
||||
let propWithoutIndices = aProp.substr(0, aProp.indexOf("["));
|
||||
obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
|
||||
if (!isObjectUsable(obj)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Then traverse the list of indices to get the actual element.
|
||||
let result;
|
||||
let arrayIndicesRegex = /\[[^\]]*\]/g;
|
||||
while ((result = arrayIndicesRegex.exec(aProp)) !== null) {
|
||||
let indexWithBrackets = result[0];
|
||||
let indexAsText = indexWithBrackets.substr(1, indexWithBrackets.length - 2);
|
||||
let index = parseInt(indexAsText);
|
||||
|
||||
if (isNaN(index)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
obj = DevToolsUtils.getProperty(obj, index);
|
||||
|
||||
if (!isObjectUsable(obj)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given Debugger.Object can be used for autocomplete.
|
||||
*
|
||||
* @param Debugger.Object aObject
|
||||
* The Debugger.Object to check.
|
||||
* @return boolean
|
||||
* True if further inspection into the object is possible, or false
|
||||
* otherwise.
|
||||
*/
|
||||
function isObjectUsable(aObject)
|
||||
{
|
||||
if (aObject == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof aObject == "object" && aObject.class == "DeadObject") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getExactMatch_impl()
|
||||
*/
|
||||
function getVariableInEnvironment(anEnvironment, aName)
|
||||
{
|
||||
return getExactMatch_impl(anEnvironment, aName, DebuggerEnvironmentSupport);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getMatchedProps_impl()
|
||||
*/
|
||||
function getMatchedPropsInEnvironment(anEnvironment, aMatch)
|
||||
{
|
||||
return getMatchedProps_impl(anEnvironment, aMatch, DebuggerEnvironmentSupport);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getMatchedProps_impl()
|
||||
*/
|
||||
function getMatchedPropsInDbgObject(aDbgObject, aMatch)
|
||||
{
|
||||
return getMatchedProps_impl(aDbgObject, aMatch, DebuggerObjectSupport);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getMatchedProps_impl()
|
||||
*/
|
||||
function getMatchedProps(aObj, aMatch)
|
||||
{
|
||||
if (typeof aObj != "object") {
|
||||
aObj = aObj.constructor.prototype;
|
||||
}
|
||||
return getMatchedProps_impl(aObj, aMatch, JSObjectSupport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all properties in the given object (and its parent prototype chain) that
|
||||
* match a given prefix.
|
||||
*
|
||||
* @param mixed aObj
|
||||
* Object whose properties we want to filter.
|
||||
* @param string aMatch
|
||||
* Filter for properties that match this string.
|
||||
* @return object
|
||||
* Object that contains the matchProp and the list of names.
|
||||
*/
|
||||
function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties})
|
||||
{
|
||||
let matches = new Set();
|
||||
let numProps = 0;
|
||||
|
||||
// We need to go up the prototype chain.
|
||||
let iter = chainIterator(aObj);
|
||||
for (let obj of iter) {
|
||||
let props = getProperties(obj);
|
||||
numProps += props.length;
|
||||
|
||||
// If there are too many properties to event attempt autocompletion,
|
||||
// or if we have already added the max number, then stop looping
|
||||
// and return the partial set that has already been discovered.
|
||||
if (numProps >= MAX_AUTOCOMPLETE_ATTEMPTS ||
|
||||
matches.size >= MAX_AUTOCOMPLETIONS) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
let prop = props[i];
|
||||
if (prop.indexOf(aMatch) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (prop.indexOf('-') > -1) {
|
||||
continue;
|
||||
}
|
||||
// If it is an array index, we can't take it.
|
||||
// This uses a trick: converting a string to a number yields NaN if
|
||||
// the operation failed, and NaN is not equal to itself.
|
||||
if (+prop != +prop) {
|
||||
matches.add(prop);
|
||||
}
|
||||
|
||||
if (matches.size >= MAX_AUTOCOMPLETIONS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
matchProp: aMatch,
|
||||
matches: [...matches],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a property value based on its name from the given object, by
|
||||
* recursively checking the object's prototype.
|
||||
*
|
||||
* @param object aObj
|
||||
* An object to look the property into.
|
||||
* @param string aName
|
||||
* The property that is looked up.
|
||||
* @returns object|undefined
|
||||
* A Debugger.Object if the property exists in the object's prototype
|
||||
* chain, undefined otherwise.
|
||||
*/
|
||||
function getExactMatch_impl(aObj, aName, {chainIterator, getProperty})
|
||||
{
|
||||
// We need to go up the prototype chain.
|
||||
let iter = chainIterator(aObj);
|
||||
for (let obj of iter) {
|
||||
let prop = getProperty(obj, aName, aObj);
|
||||
if (prop) {
|
||||
return prop.value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
var JSObjectSupport = {
|
||||
chainIterator: function*(aObj)
|
||||
{
|
||||
while (aObj) {
|
||||
yield aObj;
|
||||
aObj = Object.getPrototypeOf(aObj);
|
||||
}
|
||||
},
|
||||
|
||||
getProperties: function(aObj)
|
||||
{
|
||||
return Object.getOwnPropertyNames(aObj);
|
||||
},
|
||||
|
||||
getProperty: function()
|
||||
{
|
||||
// getProperty is unsafe with raw JS objects.
|
||||
throw "Unimplemented!";
|
||||
},
|
||||
};
|
||||
|
||||
var DebuggerObjectSupport = {
|
||||
chainIterator: function*(aObj)
|
||||
{
|
||||
while (aObj) {
|
||||
yield aObj;
|
||||
aObj = aObj.proto;
|
||||
}
|
||||
},
|
||||
|
||||
getProperties: function(aObj)
|
||||
{
|
||||
return aObj.getOwnPropertyNames();
|
||||
},
|
||||
|
||||
getProperty: function(aObj, aName, aRootObj)
|
||||
{
|
||||
// This is left unimplemented in favor to DevToolsUtils.getProperty().
|
||||
throw "Unimplemented!";
|
||||
},
|
||||
};
|
||||
|
||||
var DebuggerEnvironmentSupport = {
|
||||
chainIterator: function*(aObj)
|
||||
{
|
||||
while (aObj) {
|
||||
yield aObj;
|
||||
aObj = aObj.parent;
|
||||
}
|
||||
},
|
||||
|
||||
getProperties: function(aObj)
|
||||
{
|
||||
return aObj.names();
|
||||
},
|
||||
|
||||
getProperty: function(aObj, aName)
|
||||
{
|
||||
// TODO: we should use getVariableDescriptor() here - bug 725815.
|
||||
let result = aObj.getVariable(aName);
|
||||
// FIXME: Need actual UI, bug 941287.
|
||||
if (result === undefined || result.optimizedOut || result.missingArguments) {
|
||||
return null;
|
||||
}
|
||||
return { value: result };
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider);
|
||||
|
|
@ -10,6 +10,7 @@ if CONFIG['OS_TARGET'] != 'Android':
|
|||
|
||||
DevToolsModules(
|
||||
'client.js',
|
||||
'js-property-provider.js',
|
||||
'network-helper.js',
|
||||
'network-monitor.js',
|
||||
'server-logger-monitor.js',
|
||||
|
|
|
@ -18,7 +18,7 @@ SimpleTest.waitForExplicitFinish();
|
|||
|
||||
let gState;
|
||||
|
||||
let {MAX_AUTOCOMPLETE_ATTEMPTS,MAX_AUTOCOMPLETIONS} = require("devtools/shared/webconsole/utils");
|
||||
let {MAX_AUTOCOMPLETE_ATTEMPTS,MAX_AUTOCOMPLETIONS} = require("devtools/shared/webconsole/js-property-provider");
|
||||
|
||||
// This test runs all of its assertions twice - once with
|
||||
// evaluateJS and once with evaluateJSAsync.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
"use strict";
|
||||
const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const { JSPropertyProvider } = require("devtools/shared/webconsole/utils");
|
||||
const { JSPropertyProvider } = require("devtools/shared/webconsole/js-property-provider");
|
||||
|
||||
Components.utils.import("resource://gre/modules/jsdebugger.jsm");
|
||||
addDebuggerToGlobal(this);
|
||||
|
|
|
@ -12,7 +12,6 @@ const {isWindowIncluded} = require("devtools/shared/layout/utils");
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
|
||||
loader.lazyImporter(this, "Parser", "resource://devtools/shared/Parser.jsm");
|
||||
|
||||
// TODO: Bug 842672 - browser/ imports modules from toolkit/.
|
||||
// Note that these are only used in WebConsoleCommands, see $0 and pprint().
|
||||
|
@ -33,15 +32,8 @@ const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/;
|
|||
// Number of terminal entries for the self-xss prevention to go away
|
||||
const CONSOLE_ENTRY_THRESHOLD = 5;
|
||||
|
||||
// Provide an easy way to bail out of even attempting an autocompletion
|
||||
// if an object has way too many properties. Protects against large objects
|
||||
// with numeric values that wouldn't be tallied towards MAX_AUTOCOMPLETIONS.
|
||||
const MAX_AUTOCOMPLETE_ATTEMPTS = exports.MAX_AUTOCOMPLETE_ATTEMPTS = 100000;
|
||||
|
||||
const CONSOLE_WORKER_IDS = exports.CONSOLE_WORKER_IDS = [ 'SharedWorker', 'ServiceWorker', 'Worker' ];
|
||||
|
||||
// Prevent iterating over too many properties during autocomplete suggestions.
|
||||
const MAX_AUTOCOMPLETIONS = exports.MAX_AUTOCOMPLETIONS = 1500;
|
||||
|
||||
var WebConsoleUtils = {
|
||||
|
||||
|
@ -690,512 +682,6 @@ WebConsoleUtils.l10n.prototype = {
|
|||
},
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// JS Completer
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
(function _JSPP(WCU) {
|
||||
const STATE_NORMAL = 0;
|
||||
const STATE_QUOTE = 2;
|
||||
const STATE_DQUOTE = 3;
|
||||
|
||||
const OPEN_BODY = "{[(".split("");
|
||||
const CLOSE_BODY = "}])".split("");
|
||||
const OPEN_CLOSE_BODY = {
|
||||
"{": "}",
|
||||
"[": "]",
|
||||
"(": ")",
|
||||
};
|
||||
|
||||
/**
|
||||
* Analyses a given string to find the last statement that is interesting for
|
||||
* later completion.
|
||||
*
|
||||
* @param string aStr
|
||||
* A string to analyse.
|
||||
*
|
||||
* @returns object
|
||||
* If there was an error in the string detected, then a object like
|
||||
*
|
||||
* { err: "ErrorMesssage" }
|
||||
*
|
||||
* is returned, otherwise a object like
|
||||
*
|
||||
* {
|
||||
* state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
|
||||
* startPos: index of where the last statement begins
|
||||
* }
|
||||
*/
|
||||
function findCompletionBeginning(aStr)
|
||||
{
|
||||
let bodyStack = [];
|
||||
|
||||
let state = STATE_NORMAL;
|
||||
let start = 0;
|
||||
let c;
|
||||
for (let i = 0; i < aStr.length; i++) {
|
||||
c = aStr[i];
|
||||
|
||||
switch (state) {
|
||||
// Normal JS state.
|
||||
case STATE_NORMAL:
|
||||
if (c == '"') {
|
||||
state = STATE_DQUOTE;
|
||||
}
|
||||
else if (c == "'") {
|
||||
state = STATE_QUOTE;
|
||||
}
|
||||
else if (c == ";") {
|
||||
start = i + 1;
|
||||
}
|
||||
else if (c == " ") {
|
||||
start = i + 1;
|
||||
}
|
||||
else if (OPEN_BODY.indexOf(c) != -1) {
|
||||
bodyStack.push({
|
||||
token: c,
|
||||
start: start
|
||||
});
|
||||
start = i + 1;
|
||||
}
|
||||
else if (CLOSE_BODY.indexOf(c) != -1) {
|
||||
var last = bodyStack.pop();
|
||||
if (!last || OPEN_CLOSE_BODY[last.token] != c) {
|
||||
return {
|
||||
err: "syntax error"
|
||||
};
|
||||
}
|
||||
if (c == "}") {
|
||||
start = i + 1;
|
||||
}
|
||||
else {
|
||||
start = last.start;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Double quote state > " <
|
||||
case STATE_DQUOTE:
|
||||
if (c == "\\") {
|
||||
i++;
|
||||
}
|
||||
else if (c == "\n") {
|
||||
return {
|
||||
err: "unterminated string literal"
|
||||
};
|
||||
}
|
||||
else if (c == '"') {
|
||||
state = STATE_NORMAL;
|
||||
}
|
||||
break;
|
||||
|
||||
// Single quote state > ' <
|
||||
case STATE_QUOTE:
|
||||
if (c == "\\") {
|
||||
i++;
|
||||
}
|
||||
else if (c == "\n") {
|
||||
return {
|
||||
err: "unterminated string literal"
|
||||
};
|
||||
}
|
||||
else if (c == "'") {
|
||||
state = STATE_NORMAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
state: state,
|
||||
startPos: start
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of properties, that are possible matches based on the passed
|
||||
* Debugger.Environment/Debugger.Object and inputValue.
|
||||
*
|
||||
* @param object aDbgObject
|
||||
* When the debugger is not paused this Debugger.Object wraps the scope for autocompletion.
|
||||
* It is null if the debugger is paused.
|
||||
* @param object anEnvironment
|
||||
* When the debugger is paused this Debugger.Environment is the scope for autocompletion.
|
||||
* It is null if the debugger is not paused.
|
||||
* @param string aInputValue
|
||||
* Value that should be completed.
|
||||
* @param number [aCursor=aInputValue.length]
|
||||
* Optional offset in the input where the cursor is located. If this is
|
||||
* omitted then the cursor is assumed to be at the end of the input
|
||||
* value.
|
||||
* @returns null or object
|
||||
* If no completion valued could be computed, null is returned,
|
||||
* otherwise a object with the following form is returned:
|
||||
* {
|
||||
* matches: [ string, string, string ],
|
||||
* matchProp: Last part of the inputValue that was used to find
|
||||
* the matches-strings.
|
||||
* }
|
||||
*/
|
||||
function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor)
|
||||
{
|
||||
if (aCursor === undefined) {
|
||||
aCursor = aInputValue.length;
|
||||
}
|
||||
|
||||
let inputValue = aInputValue.substring(0, aCursor);
|
||||
|
||||
// Analyse the inputValue and find the beginning of the last part that
|
||||
// should be completed.
|
||||
let beginning = findCompletionBeginning(inputValue);
|
||||
|
||||
// There was an error analysing the string.
|
||||
if (beginning.err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the current state is not STATE_NORMAL, then we are inside of an string
|
||||
// which means that no completion is possible.
|
||||
if (beginning.state != STATE_NORMAL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let completionPart = inputValue.substring(beginning.startPos);
|
||||
let lastDot = completionPart.lastIndexOf(".");
|
||||
|
||||
// Don't complete on just an empty string.
|
||||
if (completionPart.trim() == "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Catch literals like [1,2,3] or "foo" and return the matches from
|
||||
// their prototypes.
|
||||
if (lastDot > 0) {
|
||||
let parser = new Parser();
|
||||
parser.logExceptions = false;
|
||||
let syntaxTree = parser.get(completionPart.slice(0, lastDot));
|
||||
let lastTree = syntaxTree.getLastSyntaxTree();
|
||||
let lastBody = lastTree && lastTree.AST.body[lastTree.AST.body.length - 1];
|
||||
|
||||
// Finding the last expression since we've sliced up until the dot.
|
||||
// If there were parse errors this won't exist.
|
||||
if (lastBody) {
|
||||
let expression = lastBody.expression;
|
||||
let matchProp = completionPart.slice(lastDot + 1);
|
||||
if (expression.type === "ArrayExpression") {
|
||||
return getMatchedProps(Array.prototype, matchProp);
|
||||
} else if (expression.type === "Literal" &&
|
||||
(typeof expression.value === "string")) {
|
||||
return getMatchedProps(String.prototype, matchProp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We are completing a variable / a property lookup.
|
||||
let properties = completionPart.split(".");
|
||||
let matchProp = properties.pop().trimLeft();
|
||||
let obj = aDbgObject;
|
||||
|
||||
// The first property must be found in the environment if the debugger is
|
||||
// paused.
|
||||
if (anEnvironment) {
|
||||
if (properties.length == 0) {
|
||||
return getMatchedPropsInEnvironment(anEnvironment, matchProp);
|
||||
}
|
||||
obj = getVariableInEnvironment(anEnvironment, properties.shift());
|
||||
}
|
||||
|
||||
if (!isObjectUsable(obj)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We get the rest of the properties recursively starting from the Debugger.Object
|
||||
// that wraps the first property
|
||||
for (let prop of properties) {
|
||||
prop = prop.trim();
|
||||
if (!prop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (/\[\d+\]$/.test(prop)) {
|
||||
// The property to autocomplete is a member of array. For example
|
||||
// list[i][j]..[n]. Traverse the array to get the actual element.
|
||||
obj = getArrayMemberProperty(obj, prop);
|
||||
}
|
||||
else {
|
||||
obj = DevToolsUtils.getProperty(obj, prop);
|
||||
}
|
||||
|
||||
if (!isObjectUsable(obj)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// If the final property is a primitive
|
||||
if (typeof obj != "object") {
|
||||
return getMatchedProps(obj, matchProp);
|
||||
}
|
||||
|
||||
return getMatchedPropsInDbgObject(obj, matchProp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array member of aObj for the given aProp. For example, given
|
||||
* aProp='list[0][1]' the element at [0][1] of aObj.list is returned.
|
||||
*
|
||||
* @param object aObj
|
||||
* The object to operate on.
|
||||
* @param string aProp
|
||||
* The property to return.
|
||||
* @return null or Object
|
||||
* Returns null if the property couldn't be located. Otherwise the array
|
||||
* member identified by aProp.
|
||||
*/
|
||||
function getArrayMemberProperty(aObj, aProp)
|
||||
{
|
||||
// First get the array.
|
||||
let obj = aObj;
|
||||
let propWithoutIndices = aProp.substr(0, aProp.indexOf("["));
|
||||
obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
|
||||
if (!isObjectUsable(obj)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Then traverse the list of indices to get the actual element.
|
||||
let result;
|
||||
let arrayIndicesRegex = /\[[^\]]*\]/g;
|
||||
while ((result = arrayIndicesRegex.exec(aProp)) !== null) {
|
||||
let indexWithBrackets = result[0];
|
||||
let indexAsText = indexWithBrackets.substr(1, indexWithBrackets.length - 2);
|
||||
let index = parseInt(indexAsText);
|
||||
|
||||
if (isNaN(index)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
obj = DevToolsUtils.getProperty(obj, index);
|
||||
|
||||
if (!isObjectUsable(obj)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given Debugger.Object can be used for autocomplete.
|
||||
*
|
||||
* @param Debugger.Object aObject
|
||||
* The Debugger.Object to check.
|
||||
* @return boolean
|
||||
* True if further inspection into the object is possible, or false
|
||||
* otherwise.
|
||||
*/
|
||||
function isObjectUsable(aObject)
|
||||
{
|
||||
if (aObject == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof aObject == "object" && aObject.class == "DeadObject") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getExactMatch_impl()
|
||||
*/
|
||||
function getVariableInEnvironment(anEnvironment, aName)
|
||||
{
|
||||
return getExactMatch_impl(anEnvironment, aName, DebuggerEnvironmentSupport);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getMatchedProps_impl()
|
||||
*/
|
||||
function getMatchedPropsInEnvironment(anEnvironment, aMatch)
|
||||
{
|
||||
return getMatchedProps_impl(anEnvironment, aMatch, DebuggerEnvironmentSupport);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getMatchedProps_impl()
|
||||
*/
|
||||
function getMatchedPropsInDbgObject(aDbgObject, aMatch)
|
||||
{
|
||||
return getMatchedProps_impl(aDbgObject, aMatch, DebuggerObjectSupport);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getMatchedProps_impl()
|
||||
*/
|
||||
function getMatchedProps(aObj, aMatch)
|
||||
{
|
||||
if (typeof aObj != "object") {
|
||||
aObj = aObj.constructor.prototype;
|
||||
}
|
||||
return getMatchedProps_impl(aObj, aMatch, JSObjectSupport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all properties in the given object (and its parent prototype chain) that
|
||||
* match a given prefix.
|
||||
*
|
||||
* @param mixed aObj
|
||||
* Object whose properties we want to filter.
|
||||
* @param string aMatch
|
||||
* Filter for properties that match this string.
|
||||
* @return object
|
||||
* Object that contains the matchProp and the list of names.
|
||||
*/
|
||||
function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties})
|
||||
{
|
||||
let matches = new Set();
|
||||
let numProps = 0;
|
||||
|
||||
// We need to go up the prototype chain.
|
||||
let iter = chainIterator(aObj);
|
||||
for (let obj of iter) {
|
||||
let props = getProperties(obj);
|
||||
numProps += props.length;
|
||||
|
||||
// If there are too many properties to event attempt autocompletion,
|
||||
// or if we have already added the max number, then stop looping
|
||||
// and return the partial set that has already been discovered.
|
||||
if (numProps >= MAX_AUTOCOMPLETE_ATTEMPTS ||
|
||||
matches.size >= MAX_AUTOCOMPLETIONS) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
let prop = props[i];
|
||||
if (prop.indexOf(aMatch) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (prop.indexOf('-') > -1) {
|
||||
continue;
|
||||
}
|
||||
// If it is an array index, we can't take it.
|
||||
// This uses a trick: converting a string to a number yields NaN if
|
||||
// the operation failed, and NaN is not equal to itself.
|
||||
if (+prop != +prop) {
|
||||
matches.add(prop);
|
||||
}
|
||||
|
||||
if (matches.size >= MAX_AUTOCOMPLETIONS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
matchProp: aMatch,
|
||||
matches: [...matches],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a property value based on its name from the given object, by
|
||||
* recursively checking the object's prototype.
|
||||
*
|
||||
* @param object aObj
|
||||
* An object to look the property into.
|
||||
* @param string aName
|
||||
* The property that is looked up.
|
||||
* @returns object|undefined
|
||||
* A Debugger.Object if the property exists in the object's prototype
|
||||
* chain, undefined otherwise.
|
||||
*/
|
||||
function getExactMatch_impl(aObj, aName, {chainIterator, getProperty})
|
||||
{
|
||||
// We need to go up the prototype chain.
|
||||
let iter = chainIterator(aObj);
|
||||
for (let obj of iter) {
|
||||
let prop = getProperty(obj, aName, aObj);
|
||||
if (prop) {
|
||||
return prop.value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
var JSObjectSupport = {
|
||||
chainIterator: function*(aObj)
|
||||
{
|
||||
while (aObj) {
|
||||
yield aObj;
|
||||
aObj = Object.getPrototypeOf(aObj);
|
||||
}
|
||||
},
|
||||
|
||||
getProperties: function(aObj)
|
||||
{
|
||||
return Object.getOwnPropertyNames(aObj);
|
||||
},
|
||||
|
||||
getProperty: function()
|
||||
{
|
||||
// getProperty is unsafe with raw JS objects.
|
||||
throw "Unimplemented!";
|
||||
},
|
||||
};
|
||||
|
||||
var DebuggerObjectSupport = {
|
||||
chainIterator: function*(aObj)
|
||||
{
|
||||
while (aObj) {
|
||||
yield aObj;
|
||||
aObj = aObj.proto;
|
||||
}
|
||||
},
|
||||
|
||||
getProperties: function(aObj)
|
||||
{
|
||||
return aObj.getOwnPropertyNames();
|
||||
},
|
||||
|
||||
getProperty: function(aObj, aName, aRootObj)
|
||||
{
|
||||
// This is left unimplemented in favor to DevToolsUtils.getProperty().
|
||||
throw "Unimplemented!";
|
||||
},
|
||||
};
|
||||
|
||||
var DebuggerEnvironmentSupport = {
|
||||
chainIterator: function*(aObj)
|
||||
{
|
||||
while (aObj) {
|
||||
yield aObj;
|
||||
aObj = aObj.parent;
|
||||
}
|
||||
},
|
||||
|
||||
getProperties: function(aObj)
|
||||
{
|
||||
return aObj.names();
|
||||
},
|
||||
|
||||
getProperty: function(aObj, aName)
|
||||
{
|
||||
// TODO: we should use getVariableDescriptor() here - bug 725815.
|
||||
let result = aObj.getVariable(aName);
|
||||
// FIXME: Need actual UI, bug 941287.
|
||||
if (result === undefined || result.optimizedOut || result.missingArguments) {
|
||||
return null;
|
||||
}
|
||||
return { value: result };
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider);
|
||||
})(WebConsoleUtils);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// The page errors listener
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -8,6 +8,5 @@ exports.Utils = { l10n: function() {} };
|
|||
exports.ConsoleServiceListener = function() {};
|
||||
exports.ConsoleAPIListener = function() {};
|
||||
exports.addWebConsoleCommands = function() {};
|
||||
exports.JSPropertyProvider = function() {};
|
||||
exports.ConsoleReflowListener = function() {};
|
||||
exports.CONSOLE_WORKER_IDS = [];
|
||||
|
|
Загрузка…
Ссылка в новой задаче