source upload
This commit is contained in:
1804
contrib/mORMot/SyNode/core_modules/DevTools/Debugger.js
Normal file
1804
contrib/mORMot/SyNode/core_modules/DevTools/Debugger.js
Normal file
File diff suppressed because it is too large
Load Diff
133
contrib/mORMot/SyNode/core_modules/DevTools/DevToolsUtils.js
Normal file
133
contrib/mORMot/SyNode/core_modules/DevTools/DevToolsUtils.js
Normal file
@@ -0,0 +1,133 @@
|
||||
"use strict";
|
||||
let {logError} = process.binding('debugger');
|
||||
|
||||
/**
|
||||
* Turn the error |aError| into a string, without fail.
|
||||
*/
|
||||
function safeErrorString(aError) {
|
||||
try {
|
||||
let errorString = aError.toString();
|
||||
if (typeof errorString == "string") {
|
||||
// Attempt to attach a stack to |errorString|. If it throws an error, or
|
||||
// isn't a string, don't use it.
|
||||
try {
|
||||
if (aError.stack) {
|
||||
let stack = aError.stack.toString();
|
||||
if (typeof stack == "string") {
|
||||
errorString += "\nStack: " + stack;
|
||||
}
|
||||
}
|
||||
} catch (ee) { }
|
||||
|
||||
// Append additional line and column number information to the output,
|
||||
// since it might not be part of the stringified error.
|
||||
if (typeof aError.lineNumber == "number" && typeof aError.columnNumber == "number") {
|
||||
errorString += "Line: " + aError.lineNumber + ", column: " + aError.columnNumber;
|
||||
}
|
||||
|
||||
return errorString;
|
||||
}
|
||||
} catch (ee) { }
|
||||
|
||||
// We failed to find a good error description, so do the next best thing.
|
||||
return Object.prototype.toString.call(aError);
|
||||
};
|
||||
|
||||
/**
|
||||
* Report that |aWho| threw an exception, |aException|.
|
||||
*/
|
||||
export function reportException(aWho, aException) {
|
||||
let msg = aWho + " threw an exception: " + safeErrorString(aException);
|
||||
logError(msg);
|
||||
};
|
||||
|
||||
/**
|
||||
* Safely get the property value from a Debugger.Object for a given key. Walks
|
||||
* the prototype chain until the property is found.
|
||||
*
|
||||
* @param Debugger.Object aObject
|
||||
* The Debugger.Object to get the value from.
|
||||
* @param String aKey
|
||||
* The key to look for.
|
||||
* @return Any
|
||||
*/
|
||||
export function getProperty(aObj, aKey) {
|
||||
let root = aObj;
|
||||
try {
|
||||
do {
|
||||
const desc = aObj.getOwnPropertyDescriptor(aKey);
|
||||
if (desc) {
|
||||
if ("value" in desc) {
|
||||
return desc.value;
|
||||
}
|
||||
// Call the getter if it's safe.
|
||||
return hasSafeGetter(desc) ? desc.get.call(root).return : undefined;
|
||||
}
|
||||
aObj = aObj.proto;
|
||||
} while (aObj);
|
||||
} catch (e) {
|
||||
// If anything goes wrong report the error and return undefined.
|
||||
//exports.reportException("getProperty", e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if a descriptor has a getter which doesn't call into JavaScript.
|
||||
*
|
||||
* @param Object aDesc
|
||||
* The descriptor to check for a safe getter.
|
||||
* @return Boolean
|
||||
* Whether a safe getter was found.
|
||||
*/
|
||||
export function hasSafeGetter(aDesc) {
|
||||
// Scripted functions that are CCWs will not appear scripted until after
|
||||
// unwrapping.
|
||||
try {
|
||||
let fn = aDesc.get.unwrap();
|
||||
return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
|
||||
} catch(e) {
|
||||
// Avoid exception 'Object in compartment marked as invisible to Debugger'
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Calls the property with the given `name` on the given `object`, where
|
||||
// `name` is a string, and `object` a Debugger.Object instance.
|
||||
///
|
||||
// This function uses only the Debugger.Object API to call the property. It
|
||||
// avoids the use of unsafeDeference. This is useful for example in workers,
|
||||
// where unsafeDereference will return an opaque security wrapper to the
|
||||
// referent.
|
||||
export function callPropertyOnObject(object, name) {
|
||||
// Find the property.
|
||||
let descriptor;
|
||||
let proto = object;
|
||||
do {
|
||||
descriptor = proto.getOwnPropertyDescriptor(name);
|
||||
if (descriptor !== undefined) {
|
||||
break;
|
||||
}
|
||||
proto = proto.proto;
|
||||
} while (proto !== null);
|
||||
if (descriptor === undefined) {
|
||||
throw new Error("No such property");
|
||||
}
|
||||
let value = descriptor.value;
|
||||
if (typeof value !== "object" || value === null || !("callable" in value)) {
|
||||
throw new Error("Not a callable object.");
|
||||
}
|
||||
|
||||
// Call the property.
|
||||
let result = value.call(object);
|
||||
if (result === null) {
|
||||
throw new Error("Code was terminated.");
|
||||
}
|
||||
if ("throw" in result) {
|
||||
throw result.throw;
|
||||
}
|
||||
return result.return;
|
||||
}
|
||||
|
||||
|
||||
//exports.callPropertyOnObject = callPropertyOnObject;
|
@@ -0,0 +1,826 @@
|
||||
import * as DevToolsUtils from 'DevTools/DevToolsUtils.js';
|
||||
let {global} = process.binding('debugger');
|
||||
|
||||
const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
|
||||
"Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
|
||||
"Float64Array"];
|
||||
|
||||
|
||||
// Number of items to preview in objects, arrays, maps, sets, lists,
|
||||
// collections, etc.
|
||||
const OBJECT_PREVIEW_MAX_ITEMS = 10;
|
||||
|
||||
let _ObjectActorPreviewers = {
|
||||
String: [function (objectActor, grip) {
|
||||
return wrappedPrimitivePreviewer("String", String, objectActor, grip);
|
||||
}],
|
||||
|
||||
Boolean: [function (objectActor, grip) {
|
||||
return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip);
|
||||
}],
|
||||
|
||||
Number: [function (objectActor, grip) {
|
||||
return wrappedPrimitivePreviewer("Number", Number, objectActor, grip);
|
||||
}],
|
||||
|
||||
Function: [function (objectActor, grip) {
|
||||
let {_obj} = objectActor;
|
||||
if (_obj.name) {
|
||||
grip.name = _obj.name;
|
||||
}
|
||||
|
||||
if (_obj.displayName) {
|
||||
grip.displayName = _obj.displayName.substr(0, 500);
|
||||
}
|
||||
|
||||
if (_obj.parameterNames) {
|
||||
grip.parameterNames = _obj.parameterNames;
|
||||
}
|
||||
|
||||
// Check if the developer has added a de-facto standard displayName
|
||||
// property for us to use.
|
||||
let userDisplayName;
|
||||
try {
|
||||
userDisplayName = _obj.getOwnPropertyDescriptor("displayName");
|
||||
} catch (e) {
|
||||
// Calling getOwnPropertyDescriptor with displayName might throw
|
||||
// with "permission denied" errors for some functions.
|
||||
//dumpn(e);
|
||||
}
|
||||
|
||||
if (userDisplayName && typeof userDisplayName.value == "string" &&
|
||||
userDisplayName.value) {
|
||||
grip.userDisplayName = objectActor.getGrip(userDisplayName.value);
|
||||
}
|
||||
|
||||
//let dbgGlobal = hooks.getGlobalDebugObject();
|
||||
//if (dbgGlobal) {
|
||||
//let script = dbgGlobal.makeDebuggeeValue(_obj.unsafeDereference()).script;
|
||||
let script = _obj.script;
|
||||
if (script) {
|
||||
grip.location = {
|
||||
url: script.url,
|
||||
line: script.startLine
|
||||
};
|
||||
}
|
||||
//}
|
||||
|
||||
return true;
|
||||
}],
|
||||
|
||||
RegExp: [function (objectActor, grip) {
|
||||
let {_obj} = objectActor;
|
||||
// Avoid having any special preview for the RegExp.prototype itself.
|
||||
if (!_obj.proto || _obj.proto.class != "RegExp") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let str = RegExp.prototype.toString.call(_obj.unsafeDereference());
|
||||
grip.displayString = objectActor.getGrip(str);
|
||||
return true;
|
||||
}],
|
||||
|
||||
Date: [function (objectActor, grip) {
|
||||
let {_obj} = objectActor;
|
||||
let time = Date.prototype.getTime.call(_obj.unsafeDereference());
|
||||
|
||||
grip.preview = {
|
||||
timestamp: objectActor.getGrip(time)
|
||||
};
|
||||
return true;
|
||||
}],
|
||||
|
||||
Array: [function (objectActor, grip) {
|
||||
let {_obj} = objectActor;
|
||||
let length = DevToolsUtils.getProperty(_obj, "length");
|
||||
if (typeof length != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: length
|
||||
};
|
||||
|
||||
if (objectActor.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let raw = _obj.unsafeDereference();
|
||||
let items = grip.preview.items = [];
|
||||
|
||||
for (let i = 0; i < length; ++i) {
|
||||
// Array Xrays filter out various possibly-unsafe properties (like
|
||||
// functions, and claim that the value is undefined instead. This
|
||||
// is generally the right thing for privileged code accessing untrusted
|
||||
// objects, but quite confusing for Object previews. So we manually
|
||||
// override this protection by waiving Xrays on the array, and re-applying
|
||||
// Xrays on any indexed value props that we pull off of it.
|
||||
//let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
|
||||
let desc = Object.getOwnPropertyDescriptor(raw, i);
|
||||
if (desc && !desc.get && !desc.set) {
|
||||
//let value = Cu.unwaiveXrays(desc.value);
|
||||
let value = desc.value;
|
||||
value = makeDebuggeeValueIfNeeded(_obj, value);
|
||||
items.push(objectActor.getGrip(value));
|
||||
} else {
|
||||
items.push(null);
|
||||
}
|
||||
|
||||
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}],
|
||||
|
||||
Set: [function (objectActor, grip) {
|
||||
let {_obj} = objectActor;
|
||||
let size = DevToolsUtils.getProperty(_obj, "size");
|
||||
if (typeof size != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: size
|
||||
};
|
||||
|
||||
// Avoid recursive object grips.
|
||||
if (objectActor.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let raw = _obj.unsafeDereference();
|
||||
let items = grip.preview.items = [];
|
||||
// We currently lack XrayWrappers for Set, so when we iterate over
|
||||
// the values, the temporary iterator objects get created in the target
|
||||
// compartment. However, we _do_ have Xrays to Object now, so we end up
|
||||
// Xraying those temporary objects, and filtering access to |it.value|
|
||||
// based on whether or not it's Xrayable and/or callable, which breaks
|
||||
// the for/of iteration.
|
||||
//
|
||||
// This code is designed to handle untrusted objects, so we can safely
|
||||
// waive Xrays on the iterable, and relying on the Debugger machinery to
|
||||
// make sure we handle the resulting objects carefully.
|
||||
//for (let item of Cu.waiveXrays(Set.prototype.values.call(raw))) {
|
||||
for (let item of Set.prototype.values.call(raw)) {
|
||||
//item = Cu.unwaiveXrays(item);
|
||||
item = makeDebuggeeValueIfNeeded(_obj, item);
|
||||
items.push(objectActor.getGrip(item));
|
||||
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}],
|
||||
/*WeakSet: [function(objectActor, grip) {
|
||||
let {_obj} = objectActor;
|
||||
let raw = _obj.unsafeDereference();
|
||||
|
||||
// We currently lack XrayWrappers for WeakSet, so when we iterate over
|
||||
// the values, the temporary iterator objects get created in the target
|
||||
// compartment. However, we _do_ have Xrays to Object now, so we end up
|
||||
// Xraying those temporary objects, and filtering access to |it.value|
|
||||
// based on whether or not it's Xrayable and/or callable, which breaks
|
||||
// the for/of iteration.
|
||||
//
|
||||
// This code is designed to handle untrusted objects, so we can safely
|
||||
// waive Xrays on the iterable, and relying on the Debugger machinery to
|
||||
// make sure we handle the resulting objects carefully.
|
||||
//let keys = Cu.waiveXrays(ThreadSafeChromeUtils.nondeterministicGetWeakSetKeys(raw));
|
||||
let keys = Cu.waiveXrays(ThreadSafeChromeUtils.nondeterministicGetWeakSetKeys(raw));
|
||||
grip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: keys.length
|
||||
};
|
||||
|
||||
//// Avoid recursive object grips.
|
||||
//if (hooks.getGripDepth() > 1) {
|
||||
//return true;
|
||||
//}
|
||||
|
||||
let items = grip.preview.items = [];
|
||||
for (let item of keys) {
|
||||
//item = Cu.unwaiveXrays(item);
|
||||
item = makeDebuggeeValueIfNeeded(obj, item);
|
||||
items.push(hooks.createValueGrip(item));
|
||||
if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}],*/
|
||||
|
||||
Map: [function (objectActor, grip) {
|
||||
let {_obj} = objectActor;
|
||||
let size = DevToolsUtils.getProperty(_obj, "size");
|
||||
if (typeof size != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "MapLike",
|
||||
size: size
|
||||
};
|
||||
|
||||
if (objectActor.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let raw = _obj.unsafeDereference();
|
||||
let entries = grip.preview.entries = [];
|
||||
// Iterating over a Map via .entries goes through various intermediate
|
||||
// objects - an Iterator object, then a 2-element Array object, then the
|
||||
// actual values we care about. We don't have Xrays to Iterator objects,
|
||||
// so we get Opaque wrappers for them. And even though we have Xrays to
|
||||
// Arrays, the semantics often deny access to the entires based on the
|
||||
// nature of the values. So we need waive Xrays for the iterator object
|
||||
// and the tupes, and then re-apply them on the underlying values until
|
||||
// we fix bug 1023984.
|
||||
//
|
||||
// Even then though, we might want to continue waiving Xrays here for the
|
||||
// same reason we do so for Arrays above - this filtering behavior is likely
|
||||
// to be more confusing than beneficial in the case of Object previews.
|
||||
//for (let keyValuePair of Cu.waiveXrays(Map.prototype.entries.call(raw))) {
|
||||
for (let keyValuePair of Map.prototype.entries.call(raw)) {
|
||||
//let key = Cu.unwaiveXrays(keyValuePair[0]);
|
||||
let key = keyValuePair[0];
|
||||
//let value = Cu.unwaiveXrays(keyValuePair[1]);
|
||||
let value = keyValuePair[1];
|
||||
key = makeDebuggeeValueIfNeeded(_obj, key);
|
||||
value = makeDebuggeeValueIfNeeded(_obj, value);
|
||||
entries.push([objectActor.getGrip(key),
|
||||
objectActor.getGrip(value)]);
|
||||
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}]/*,
|
||||
|
||||
WeakMap: [function({obj, hooks}, grip) {
|
||||
let raw = obj.unsafeDereference();
|
||||
// We currently lack XrayWrappers for WeakMap, so when we iterate over
|
||||
// the values, the temporary iterator objects get created in the target
|
||||
// compartment. However, we _do_ have Xrays to Object now, so we end up
|
||||
// Xraying those temporary objects, and filtering access to |it.value|
|
||||
// based on whether or not it's Xrayable and/or callable, which breaks
|
||||
// the for/of iteration.
|
||||
//
|
||||
// This code is designed to handle untrusted objects, so we can safely
|
||||
// waive Xrays on the iterable, and relying on the Debugger machinery to
|
||||
// make sure we handle the resulting objects carefully.
|
||||
let rawEntries = Cu.waiveXrays(ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(raw));
|
||||
|
||||
grip.preview = {
|
||||
kind: "MapLike",
|
||||
size: rawEntries.length,
|
||||
};
|
||||
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let entries = grip.preview.entries = [];
|
||||
for (let key of rawEntries) {
|
||||
let value = Cu.unwaiveXrays(WeakMap.prototype.get.call(raw, key));
|
||||
key = Cu.unwaiveXrays(key);
|
||||
key = makeDebuggeeValueIfNeeded(obj, key);
|
||||
value = makeDebuggeeValueIfNeeded(obj, value);
|
||||
entries.push([hooks.createValueGrip(key),
|
||||
hooks.createValueGrip(value)]);
|
||||
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}],
|
||||
|
||||
DOMStringMap: [function({obj, hooks}, grip, rawObj) {
|
||||
if (!rawObj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let keys = obj.getOwnPropertyNames();
|
||||
grip.preview = {
|
||||
kind: "MapLike",
|
||||
size: keys.length,
|
||||
};
|
||||
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let entries = grip.preview.entries = [];
|
||||
for (let key of keys) {
|
||||
let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
|
||||
entries.push([key, hooks.createValueGrip(value)]);
|
||||
if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}],*/
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic previewer for classes wrapping primitives, like String,
|
||||
* Number and Boolean.
|
||||
*
|
||||
* @param string className
|
||||
* Class name to expect.
|
||||
* @param object classObj
|
||||
* The class to expect, eg. String. The valueOf() method of the class is
|
||||
* invoked on the given object.
|
||||
* @param ObjectActor objectActor
|
||||
* The object actor
|
||||
* @param Object grip
|
||||
* The result grip to fill in
|
||||
* @return Booolean true if the object was handled, false otherwise
|
||||
*/
|
||||
function wrappedPrimitivePreviewer(className, classObj, objectActor, grip) {
|
||||
let {_obj} = objectActor;
|
||||
|
||||
if (!_obj.proto || _obj.proto.class != className) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let raw = _obj.unsafeDereference();
|
||||
let v = null;
|
||||
try {
|
||||
v = classObj.prototype.valueOf.call(raw);
|
||||
} catch (ex) {
|
||||
// valueOf() can throw if the raw JS object is "misbehaved".
|
||||
return false;
|
||||
}
|
||||
|
||||
if (v === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let canHandle = GenericObject(objectActor, grip, className === "String");
|
||||
if (!canHandle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview.wrappedValue = objectActor.getGrip(makeDebuggeeValueIfNeeded(_obj, v));
|
||||
return true;
|
||||
}
|
||||
|
||||
function GenericObject(objectActor, grip, specialStringBehavior = false) {
|
||||
let {_obj} = objectActor;
|
||||
if (grip.preview || grip.displayString || objectActor.getGripDepth() > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let i = 0, names = [];
|
||||
let preview = grip.preview = {
|
||||
kind: "Object",
|
||||
ownProperties: {}//Object.create(null)
|
||||
};
|
||||
|
||||
try {
|
||||
names = _obj.getOwnPropertyNames();
|
||||
} catch (ex) {
|
||||
// Calling getOwnPropertyNames() on some wrapped native prototypes is not
|
||||
// allowed: "cannot modify properties of a WrappedNative". See bug 952093.
|
||||
}
|
||||
|
||||
preview.ownPropertiesLength = names.length;
|
||||
|
||||
let length;
|
||||
if (specialStringBehavior) {
|
||||
length = DevToolsUtils.getProperty(_obj, "length");
|
||||
if (typeof length != "number") {
|
||||
specialStringBehavior = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (let name of names) {
|
||||
if (specialStringBehavior && /^[0-9]+$/.test(name)) {
|
||||
let num = parseInt(name, 10);
|
||||
if (num.toString() === name && num >= 0 && num < length) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let desc = objectActor._propertyDescriptor(name, true);
|
||||
if (!desc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
preview.ownProperties[name] = desc;
|
||||
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i < OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
preview.safeGetterValues = objectActor._findSafeGetterValues(
|
||||
Object.keys(preview.ownProperties),
|
||||
OBJECT_PREVIEW_MAX_ITEMS - i);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a debuggee value for the given object, if needed. Primitive values
|
||||
* are left the same.
|
||||
*
|
||||
* Use case: you have a raw JS object (after unsafe dereference) and you want to
|
||||
* send it to the client. In that case you need to use an ObjectActor which
|
||||
* requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
|
||||
* method works only for JS objects and functions.
|
||||
*
|
||||
* @param Debugger.Object obj
|
||||
* @param any value
|
||||
* @return object
|
||||
*/
|
||||
function makeDebuggeeValueIfNeeded(obj, value) {
|
||||
if (value && (typeof value == "object" || typeof value == "function")) {
|
||||
return obj.makeDebuggeeValue(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// Preview functions that do not rely on the object class.
|
||||
_ObjectActorPreviewers.Object = [
|
||||
function TypedArray(objectActor, grip) {
|
||||
let {_obj} = objectActor;
|
||||
if (TYPED_ARRAY_CLASSES.indexOf(_obj.class) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let length = DevToolsUtils.getProperty(_obj, "length");
|
||||
if (typeof length != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: length
|
||||
};
|
||||
|
||||
if (objectActor.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let raw = _obj.unsafeDereference();
|
||||
//let global = Cu.getGlobalForObject(DebuggerServer);
|
||||
|
||||
let classProto = global[_obj.class].prototype;
|
||||
// The Xray machinery for TypedArrays denies indexed access on the grounds
|
||||
// that it's slow, and advises callers to do a structured clone instead.
|
||||
//let safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
|
||||
// OBJECT_PREVIEW_MAX_ITEMS), global);
|
||||
let safeView = classProto.subarray.call(raw, 0,
|
||||
OBJECT_PREVIEW_MAX_ITEMS);
|
||||
let items = grip.preview.items = [];
|
||||
for (let i = 0; i < safeView.length; i++) {
|
||||
items.push(safeView[i]);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function Error(objectActor, grip) {
|
||||
let {_obj} = objectActor;
|
||||
switch (_obj.class) {
|
||||
case "Error":
|
||||
case "EvalError":
|
||||
case "RangeError":
|
||||
case "ReferenceError":
|
||||
case "SyntaxError":
|
||||
case "TypeError":
|
||||
case "URIError":
|
||||
let name = DevToolsUtils.getProperty(_obj, "name");
|
||||
let msg = DevToolsUtils.getProperty(_obj, "message");
|
||||
let stack = DevToolsUtils.getProperty(_obj, "stack");
|
||||
let fileName = DevToolsUtils.getProperty(_obj, "fileName");
|
||||
let lineNumber = DevToolsUtils.getProperty(_obj, "lineNumber");
|
||||
let columnNumber = DevToolsUtils.getProperty(_obj, "columnNumber");
|
||||
grip.preview = {
|
||||
kind: "Error",
|
||||
name: objectActor.getGrip(name),
|
||||
message: objectActor.getGrip(msg),
|
||||
stack: objectActor.getGrip(stack),
|
||||
fileName: objectActor.getGrip(fileName),
|
||||
lineNumber: objectActor.getGrip(lineNumber),
|
||||
columnNumber: objectActor.getGrip(columnNumber)
|
||||
};
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/*function CSSMediaRule({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSMediaRule)) {
|
||||
return false;
|
||||
}
|
||||
grip.preview = {
|
||||
kind: "ObjectWithText",
|
||||
text: hooks.createValueGrip(rawObj.conditionText),
|
||||
};
|
||||
return true;
|
||||
},
|
||||
|
||||
function CSSStyleRule({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSStyleRule)) {
|
||||
return false;
|
||||
}
|
||||
grip.preview = {
|
||||
kind: "ObjectWithText",
|
||||
text: hooks.createValueGrip(rawObj.selectorText),
|
||||
};
|
||||
return true;
|
||||
},
|
||||
|
||||
function ObjectWithURL({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSImportRule ||
|
||||
rawObj instanceof Ci.nsIDOMCSSStyleSheet ||
|
||||
rawObj instanceof Ci.nsIDOMLocation ||
|
||||
rawObj instanceof Ci.nsIDOMWindow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let url;
|
||||
if (rawObj instanceof Ci.nsIDOMWindow && rawObj.location) {
|
||||
url = rawObj.location.href;
|
||||
} else if (rawObj.href) {
|
||||
url = rawObj.href;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "ObjectWithURL",
|
||||
url: hooks.createValueGrip(url),
|
||||
};
|
||||
|
||||
return true;
|
||||
},*/
|
||||
|
||||
/*function ArrayLike(objectActor, grip, rawObj) {
|
||||
let {_obj} = objectActor;
|
||||
if (isWorker || !rawObj ||
|
||||
obj.class != "DOMStringList" &&
|
||||
obj.class != "DOMTokenList" && !(rawObj instanceof Ci.nsIDOMMozNamedAttrMap ||
|
||||
rawObj instanceof Ci.nsIDOMCSSRuleList ||
|
||||
rawObj instanceof Ci.nsIDOMCSSValueList ||
|
||||
rawObj instanceof Ci.nsIDOMFileList ||
|
||||
rawObj instanceof Ci.nsIDOMFontFaceList ||
|
||||
rawObj instanceof Ci.nsIDOMMediaList ||
|
||||
rawObj instanceof Ci.nsIDOMNodeList ||
|
||||
rawObj instanceof Ci.nsIDOMStyleSheetList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof rawObj.length != "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: rawObj.length,
|
||||
};
|
||||
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let items = grip.preview.items = [];
|
||||
|
||||
for (let i = 0; i < rawObj.length &&
|
||||
items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
|
||||
let value = makeDebuggeeValueIfNeeded(obj, rawObj[i]);
|
||||
items.push(hooks.createValueGrip(value));
|
||||
}
|
||||
|
||||
return true;
|
||||
},*/
|
||||
|
||||
/*function CSSStyleDeclaration({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMCSSStyleDeclaration)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "MapLike",
|
||||
size: rawObj.length,
|
||||
};
|
||||
|
||||
let entries = grip.preview.entries = [];
|
||||
|
||||
for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
|
||||
i < rawObj.length; i++) {
|
||||
let prop = rawObj[i];
|
||||
let value = rawObj.getPropertyValue(prop);
|
||||
entries.push([prop, hooks.createValueGrip(value)]);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function DOMNode({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || obj.class == "Object" || !rawObj || !(rawObj instanceof Ci.nsIDOMNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let preview = grip.preview = {
|
||||
kind: "DOMNode",
|
||||
nodeType: rawObj.nodeType,
|
||||
nodeName: rawObj.nodeName,
|
||||
};
|
||||
|
||||
if (rawObj instanceof Ci.nsIDOMDocument && rawObj.location) {
|
||||
preview.location = hooks.createValueGrip(rawObj.location.href);
|
||||
} else if (rawObj instanceof Ci.nsIDOMDocumentFragment) {
|
||||
preview.childNodesLength = rawObj.childNodes.length;
|
||||
|
||||
if (hooks.getGripDepth() < 2) {
|
||||
preview.childNodes = [];
|
||||
for (let node of rawObj.childNodes) {
|
||||
let actor = hooks.createValueGrip(obj.makeDebuggeeValue(node));
|
||||
preview.childNodes.push(actor);
|
||||
if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (rawObj instanceof Ci.nsIDOMElement) {
|
||||
// Add preview for DOM element attributes.
|
||||
if (rawObj instanceof Ci.nsIDOMHTMLElement) {
|
||||
preview.nodeName = preview.nodeName.toLowerCase();
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
preview.attributes = {};
|
||||
preview.attributesLength = rawObj.attributes.length;
|
||||
for (let attr of rawObj.attributes) {
|
||||
preview.attributes[attr.nodeName] = hooks.createValueGrip(attr.value);
|
||||
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (rawObj instanceof Ci.nsIDOMAttr) {
|
||||
preview.value = hooks.createValueGrip(rawObj.value);
|
||||
} else if (rawObj instanceof Ci.nsIDOMText ||
|
||||
rawObj instanceof Ci.nsIDOMComment) {
|
||||
preview.textContent = hooks.createValueGrip(rawObj.textContent);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function DOMEvent({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMEvent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let preview = grip.preview = {
|
||||
kind: "DOMEvent",
|
||||
type: rawObj.type,
|
||||
properties: Object.create(null),
|
||||
};
|
||||
|
||||
if (hooks.getGripDepth() < 2) {
|
||||
let target = obj.makeDebuggeeValue(rawObj.target);
|
||||
preview.target = hooks.createValueGrip(target);
|
||||
}
|
||||
|
||||
let props = [];
|
||||
if (rawObj instanceof Ci.nsIDOMMouseEvent) {
|
||||
props.push("buttons", "clientX", "clientY", "layerX", "layerY");
|
||||
} else if (rawObj instanceof Ci.nsIDOMKeyEvent) {
|
||||
let modifiers = [];
|
||||
if (rawObj.altKey) {
|
||||
modifiers.push("Alt");
|
||||
}
|
||||
if (rawObj.ctrlKey) {
|
||||
modifiers.push("Control");
|
||||
}
|
||||
if (rawObj.metaKey) {
|
||||
modifiers.push("Meta");
|
||||
}
|
||||
if (rawObj.shiftKey) {
|
||||
modifiers.push("Shift");
|
||||
}
|
||||
preview.eventKind = "key";
|
||||
preview.modifiers = modifiers;
|
||||
|
||||
props.push("key", "charCode", "keyCode");
|
||||
} else if (rawObj instanceof Ci.nsIDOMTransitionEvent) {
|
||||
props.push("propertyName", "pseudoElement");
|
||||
} else if (rawObj instanceof Ci.nsIDOMAnimationEvent) {
|
||||
props.push("animationName", "pseudoElement");
|
||||
} else if (rawObj instanceof Ci.nsIDOMClipboardEvent) {
|
||||
props.push("clipboardData");
|
||||
}
|
||||
|
||||
// Add event-specific properties.
|
||||
for (let prop of props) {
|
||||
let value = rawObj[prop];
|
||||
if (value && (typeof value == "object" || typeof value == "function")) {
|
||||
// Skip properties pointing to objects.
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
continue;
|
||||
}
|
||||
value = obj.makeDebuggeeValue(value);
|
||||
}
|
||||
preview.properties[prop] = hooks.createValueGrip(value);
|
||||
}
|
||||
|
||||
// Add any properties we find on the event object.
|
||||
if (!props.length) {
|
||||
let i = 0;
|
||||
for (let prop in rawObj) {
|
||||
let value = rawObj[prop];
|
||||
if (prop == "target" || prop == "type" || value === null ||
|
||||
typeof value == "function") {
|
||||
continue;
|
||||
}
|
||||
if (value && typeof value == "object") {
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
continue;
|
||||
}
|
||||
value = obj.makeDebuggeeValue(value);
|
||||
}
|
||||
preview.properties[prop] = hooks.createValueGrip(value);
|
||||
if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
function DOMException({obj, hooks}, grip, rawObj) {
|
||||
if (isWorker || !rawObj || !(rawObj instanceof Ci.nsIDOMDOMException)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "DOMException",
|
||||
name: hooks.createValueGrip(rawObj.name),
|
||||
message: hooks.createValueGrip(rawObj.message),
|
||||
code: hooks.createValueGrip(rawObj.code),
|
||||
result: hooks.createValueGrip(rawObj.result),
|
||||
filename: hooks.createValueGrip(rawObj.filename),
|
||||
lineNumber: hooks.createValueGrip(rawObj.lineNumber),
|
||||
columnNumber: hooks.createValueGrip(rawObj.columnNumber),
|
||||
};
|
||||
|
||||
return true;
|
||||
},*/
|
||||
|
||||
/*function PseudoArray({obj, hooks}, grip, rawObj) {
|
||||
let length = 0;
|
||||
|
||||
// Making sure all keys are numbers from 0 to length-1
|
||||
let keys = obj.getOwnPropertyNames();
|
||||
if (keys.length == 0) {
|
||||
return false;
|
||||
}
|
||||
for (let key of keys) {
|
||||
if (isNaN(key) || key != length++) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "ArrayLike",
|
||||
length: length,
|
||||
};
|
||||
|
||||
// Avoid recursive object grips.
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let items = grip.preview.items = [];
|
||||
|
||||
let i = 0;
|
||||
for (let key of keys) {
|
||||
if (rawObj.hasOwnProperty(key) && i++ < OBJECT_PREVIEW_MAX_ITEMS) {
|
||||
let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
|
||||
items.push(hooks.createValueGrip(value));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},*/
|
||||
|
||||
GenericObject
|
||||
];
|
||||
|
||||
export const ObjectActorPreviewers = _ObjectActorPreviewers;
|
@@ -0,0 +1,538 @@
|
||||
"use strict";
|
||||
import * as DevToolsUtils from 'DevTools/DevToolsUtils.js';
|
||||
|
||||
//todo
|
||||
//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.
|
||||
export const MAX_AUTOCOMPLETE_ATTEMPTS = 100000;
|
||||
// Prevent iterating over too many properties during autocomplete suggestions.
|
||||
export const 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 = {
|
||||
"{": "}",
|
||||
"[": "]",
|
||||
"(": ")"
|
||||
};
|
||||
|
||||
function hasArrayIndex(str) {
|
||||
return /\[\d+\]$/.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* }
|
||||
*/
|
||||
export 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.
|
||||
//todo
|
||||
/*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 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);
|
||||
}
|
||||
|
||||
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 i = 0; i < properties.length; i++) {
|
||||
let prop = properties[i].trim();
|
||||
if (!prop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hasArrayIndex(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, null, 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. Should be null if aEnv is passed.
|
||||
* @param object aEnv
|
||||
* The Environment to operate in. Should be null if aObj is passed.
|
||||
* @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, aEnv, aProp) {
|
||||
// First get the array.
|
||||
let obj = aObj;
|
||||
let propWithoutIndices = aProp.substr(0, aProp.indexOf("["));
|
||||
|
||||
if (aEnv) {
|
||||
obj = getVariableInEnvironment(aEnv, propWithoutIndices);
|
||||
} else {
|
||||
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) {
|
||||
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;
|
||||
},
|
||||
|
||||
getProperty: function (aObj, aName) {
|
||||
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) {
|
||||
}
|
||||
|
||||
// FIXME: Need actual UI, bug 941287.
|
||||
if (result === undefined || result.optimizedOut || result.missingArguments) {
|
||||
return null;
|
||||
}
|
||||
return {value: result};
|
||||
}
|
||||
};
|
139
contrib/mORMot/SyNode/core_modules/DevTools/stringify.js
Normal file
139
contrib/mORMot/SyNode/core_modules/DevTools/stringify.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import * as DevToolsUtils from 'DevTools/DevToolsUtils.js';
|
||||
|
||||
/**
|
||||
* Stringify a Debugger.Object based on its class.
|
||||
*
|
||||
* @param Debugger.Object obj
|
||||
* The object to stringify.
|
||||
* @return String
|
||||
* The stringification for the object.
|
||||
*/
|
||||
export function stringify(obj) {
|
||||
if (obj.class == "DeadObject") {
|
||||
const error = new Error("Dead object encountered.");
|
||||
DevToolsUtils.reportException("stringify", error);
|
||||
return "<dead object>";
|
||||
}
|
||||
|
||||
const stringifier = stringifiers[obj.class] || stringifiers.Object;
|
||||
|
||||
try {
|
||||
return stringifier(obj);
|
||||
} catch (e) {
|
||||
DevToolsUtils.reportException("stringify", e);
|
||||
return "<failed to stringify object>";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if a given value is non-primitive.
|
||||
*
|
||||
* @param Any value
|
||||
* The value to test.
|
||||
* @return Boolean
|
||||
* Whether the value is non-primitive.
|
||||
*/
|
||||
function isObject(value) {
|
||||
const type = typeof value;
|
||||
return type == "object" ? value !== null : type == "function";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a function that can safely stringify Debugger.Objects of a given
|
||||
* builtin type.
|
||||
*
|
||||
* @param Function ctor
|
||||
* The builtin class constructor.
|
||||
* @return Function
|
||||
* The stringifier for the class.
|
||||
*/
|
||||
function createBuiltinStringifier(ctor) {
|
||||
return obj => ctor.prototype.toString.call(obj.unsafeDereference());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stringify a Debugger.Object-wrapped Error instance.
|
||||
*
|
||||
* @param Debugger.Object obj
|
||||
* The object to stringify.
|
||||
* @return String
|
||||
* The stringification of the object.
|
||||
*/
|
||||
function errorStringify(obj) {
|
||||
let name = DevToolsUtils.getProperty(obj, "name");
|
||||
if (name === "" || name === undefined) {
|
||||
name = obj.class;
|
||||
} else if (isObject(name)) {
|
||||
name = stringify(name);
|
||||
}
|
||||
|
||||
let message = DevToolsUtils.getProperty(obj, "message");
|
||||
if (isObject(message)) {
|
||||
message = stringify(message);
|
||||
}
|
||||
|
||||
if (message === "" || message === undefined) {
|
||||
return name;
|
||||
}
|
||||
return name + ": " + message;
|
||||
}
|
||||
|
||||
// Used to prevent infinite recursion when an array is found inside itself.
|
||||
let seen = null;
|
||||
|
||||
const stringifiers = {
|
||||
Error: errorStringify,
|
||||
EvalError: errorStringify,
|
||||
RangeError: errorStringify,
|
||||
ReferenceError: errorStringify,
|
||||
SyntaxError: errorStringify,
|
||||
TypeError: errorStringify,
|
||||
URIError: errorStringify,
|
||||
Boolean: createBuiltinStringifier(Boolean),
|
||||
Function: createBuiltinStringifier(Function),
|
||||
Number: createBuiltinStringifier(Number),
|
||||
RegExp: createBuiltinStringifier(RegExp),
|
||||
String: createBuiltinStringifier(String),
|
||||
Object: obj => "[object " + obj.class + "]",
|
||||
Array: obj => {
|
||||
// If we're at the top level then we need to create the Set for tracking
|
||||
// previously stringified arrays.
|
||||
const topLevel = !seen;
|
||||
if (topLevel) {
|
||||
seen = new Set();
|
||||
} else if (seen.has(obj)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
seen.add(obj);
|
||||
|
||||
const len = DevToolsUtils.getProperty(obj, "length");
|
||||
let string = "";
|
||||
|
||||
// The following check is only required because the debuggee could possibly
|
||||
// be a Proxy and return any value. For normal objects, array.length is
|
||||
// always a non-negative integer.
|
||||
if (typeof len == "number" && len > 0) {
|
||||
for (let i = 0; i < len; i++) {
|
||||
const desc = obj.getOwnPropertyDescriptor(i);
|
||||
if (desc) {
|
||||
const { value } = desc;
|
||||
if (value != null) {
|
||||
string += isObject(value) ? stringify(value) : value;
|
||||
}
|
||||
}
|
||||
|
||||
if (i < len - 1) {
|
||||
string += ",";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (topLevel) {
|
||||
seen = null;
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user