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:
Brian Grinstead 2015-10-26 11:55:40 -07:00
Родитель f959a4d1ef
Коммит f2795041dc
9 изменённых файлов: 530 добавлений и 520 удалений

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

@ -17,7 +17,7 @@ function test() {
} }
function consoleOpened(HUD) { 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", {}); let tmp = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
tmp.addDebuggerToGlobal(tmp); tmp.addDebuggerToGlobal(tmp);

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

@ -16,7 +16,7 @@ function test() {
function testPropertyProvider({browser}) { function testPropertyProvider({browser}) {
browser.removeEventListener("load", testPropertyProvider, true); 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", {}); let tmp = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
tmp.addDebuggerToGlobal(tmp); 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, "ConsoleProgressListener", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "events", "sdk/event/core"); loader.lazyRequireGetter(this, "events", "sdk/event/core");
loader.lazyRequireGetter(this, "ServerLoggingListener", "devtools/shared/webconsole/server-logger", true); 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", for (let name of ["WebConsoleUtils", "ConsoleServiceListener",
"ConsoleAPIListener", "addWebConsoleCommands", "JSPropertyProvider", "ConsoleAPIListener", "addWebConsoleCommands",
"ConsoleReflowListener", "CONSOLE_WORKER_IDS"]) { "ConsoleReflowListener", "CONSOLE_WORKER_IDS"]) {
Object.defineProperty(this, name, { Object.defineProperty(this, name, {
get: function(prop) { 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( DevToolsModules(
'client.js', 'client.js',
'js-property-provider.js',
'network-helper.js', 'network-helper.js',
'network-monitor.js', 'network-monitor.js',
'server-logger-monitor.js', 'server-logger-monitor.js',

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

@ -18,7 +18,7 @@ SimpleTest.waitForExplicitFinish();
let gState; 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 // This test runs all of its assertions twice - once with
// evaluateJS and once with evaluateJSAsync. // evaluateJS and once with evaluateJSAsync.

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

@ -4,7 +4,7 @@
"use strict"; "use strict";
const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {}); 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"); Components.utils.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(this); addDebuggerToGlobal(this);

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

@ -12,7 +12,6 @@ const {isWindowIncluded} = require("devtools/shared/layout/utils");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
loader.lazyImporter(this, "Services", "resource://gre/modules/Services.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/. // TODO: Bug 842672 - browser/ imports modules from toolkit/.
// Note that these are only used in WebConsoleCommands, see $0 and pprint(). // 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 // Number of terminal entries for the self-xss prevention to go away
const CONSOLE_ENTRY_THRESHOLD = 5; 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' ]; 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 = { 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 // The page errors listener
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

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

@ -8,6 +8,5 @@ exports.Utils = { l10n: function() {} };
exports.ConsoleServiceListener = function() {}; exports.ConsoleServiceListener = function() {};
exports.ConsoleAPIListener = function() {}; exports.ConsoleAPIListener = function() {};
exports.addWebConsoleCommands = function() {}; exports.addWebConsoleCommands = function() {};
exports.JSPropertyProvider = function() {};
exports.ConsoleReflowListener = function() {}; exports.ConsoleReflowListener = function() {};
exports.CONSOLE_WORKER_IDS = []; exports.CONSOLE_WORKER_IDS = [];