source upload

This commit is contained in:
Razor12911
2022-01-17 22:16:47 +02:00
parent 12936d065b
commit 098e8c48de
1778 changed files with 1206749 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View 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;

View File

@@ -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;

View File

@@ -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};
}
};

View 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;
}
};