зеркало из 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) {
|
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 = [];
|
||||||
|
|
Загрузка…
Ссылка в новой задаче