2016-01-25 19:19:58 +00:00
|
|
|
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
|
|
/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
|
2015-10-26 18:55:40 +00:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
2016-01-25 19:19:58 +00:00
|
|
|
* 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/. */
|
2015-10-26 18:55:40 +00:00
|
|
|
|
|
|
|
"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 = {
|
|
|
|
"{": "}",
|
|
|
|
"[": "]",
|
|
|
|
"(": ")",
|
|
|
|
};
|
|
|
|
|
2015-12-03 19:35:33 +00:00
|
|
|
function hasArrayIndex(str) {
|
|
|
|
return /\[\d+\]$/.test(str);
|
|
|
|
}
|
|
|
|
|
2015-10-26 18:55:40 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
|
2015-12-03 19:35:33 +00:00
|
|
|
// The first property must be found in the environment of the paused debugger
|
|
|
|
// or of the global lexical scope.
|
|
|
|
let env = anEnvironment || obj.asEnvironment();
|
|
|
|
|
|
|
|
if (properties.length === 0) {
|
|
|
|
return getMatchedPropsInEnvironment(env, matchProp);
|
|
|
|
}
|
|
|
|
|
|
|
|
let firstProp = properties.shift().trim();
|
|
|
|
if (firstProp === "this") {
|
|
|
|
// Special case for 'this' - try to get the Object from the Environment.
|
|
|
|
// No problem if it throws, we will just not autocomplete.
|
|
|
|
try {
|
|
|
|
obj = env.object;
|
|
|
|
} catch(e) { }
|
|
|
|
}
|
|
|
|
else if (hasArrayIndex(firstProp)) {
|
|
|
|
obj = getArrayMemberProperty(null, env, firstProp);
|
|
|
|
} else {
|
|
|
|
obj = getVariableInEnvironment(env, firstProp);
|
2015-10-26 18:55:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!isObjectUsable(obj)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We get the rest of the properties recursively starting from the Debugger.Object
|
|
|
|
// that wraps the first property
|
2015-12-02 17:24:16 +00:00
|
|
|
for (let i = 0; i < properties.length; i++) {
|
|
|
|
let prop = properties[i].trim();
|
2015-10-26 18:55:40 +00:00
|
|
|
if (!prop) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-12-03 19:35:33 +00:00
|
|
|
if (hasArrayIndex(prop)) {
|
2015-10-26 18:55:40 +00:00
|
|
|
// The property to autocomplete is a member of array. For example
|
|
|
|
// list[i][j]..[n]. Traverse the array to get the actual element.
|
2015-12-03 19:35:33 +00:00
|
|
|
obj = getArrayMemberProperty(obj, null, prop);
|
2015-10-26 18:55:40 +00:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-12-03 19:35:33 +00:00
|
|
|
return getMatchedPropsInDbgObject(obj, matchProp);
|
2015-10-26 18:55:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2015-12-03 19:35:33 +00:00
|
|
|
* The object to operate on. Should be null if aEnv is passed.
|
|
|
|
* @param object aEnv
|
|
|
|
* The Environment to operate in. Should be null if aObj is passed.
|
2015-10-26 18:55:40 +00:00
|
|
|
* @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.
|
|
|
|
*/
|
2015-12-03 19:35:33 +00:00
|
|
|
function getArrayMemberProperty(aObj, aEnv, aProp)
|
2015-10-26 18:55:40 +00:00
|
|
|
{
|
|
|
|
// First get the array.
|
|
|
|
let obj = aObj;
|
|
|
|
let propWithoutIndices = aProp.substr(0, aProp.indexOf("["));
|
2015-12-03 19:35:33 +00:00
|
|
|
|
|
|
|
if (aEnv) {
|
|
|
|
obj = getVariableInEnvironment(aEnv, propWithoutIndices);
|
|
|
|
} else {
|
|
|
|
obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
|
|
|
|
}
|
|
|
|
|
2015-10-26 18:55:40 +00:00
|
|
|
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)
|
|
|
|
{
|
2015-12-03 19:35:33 +00:00
|
|
|
return aObj.getOwnPropertyNames();
|
2015-10-26 18:55:40 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2015-12-03 19:35:33 +00:00
|
|
|
let names = aObj.names();
|
|
|
|
|
|
|
|
// Include 'this' in results (in sorted order)
|
|
|
|
for (let i = 0; i < names.length; i++) {
|
|
|
|
if (i === names.length - 1 || names[i+1] > "this") {
|
|
|
|
names.splice(i+1, 0, "this");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return names;
|
2015-10-26 18:55:40 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
getProperty: function(aObj, aName)
|
|
|
|
{
|
2015-12-03 19:35:33 +00:00
|
|
|
let result;
|
|
|
|
// Try/catch since aName can be anything, and getVariable throws if
|
|
|
|
// it's not a valid ECMAScript identifier name
|
|
|
|
try {
|
|
|
|
// TODO: we should use getVariableDescriptor() here - bug 725815.
|
|
|
|
result = aObj.getVariable(aName);
|
|
|
|
} catch(e) { }
|
|
|
|
|
2015-10-26 18:55:40 +00:00
|
|
|
// FIXME: Need actual UI, bug 941287.
|
|
|
|
if (result === undefined || result.optimizedOut || result.missingArguments) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return { value: result };
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider);
|
|
|
|
|
2015-12-03 19:35:33 +00:00
|
|
|
// Export a version that will throw (for tests)
|
|
|
|
exports.FallibleJSPropertyProvider = JSPropertyProvider;
|