]> git.saurik.com Git - apple/javascriptcore.git/blobdiff - inspector/InjectedScriptSource.js
JavaScriptCore-7601.1.46.3.tar.gz
[apple/javascriptcore.git] / inspector / InjectedScriptSource.js
index db331cf91f5074e612e5bb3bc6e20c036a56801a..e576139b7b9d8f71769a402cbe2bd7e96d8a4e99 100644 (file)
@@ -1,5 +1,6 @@
 /*
- * Copyright (C) 2007 Apple Inc.  All rights reserved.
+ * Copyright (C) 2007, 2014-2015 Apple Inc.  All rights reserved.
+ * Copyright (C) 2013 Google Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 
 //# sourceURL=__WebInspectorInjectedScript__
 
-/**
- * @param {InjectedScriptHost} InjectedScriptHost
- * @param {GlobalObject} inspectedGlobalObject
- * @param {number} injectedScriptId
- */
 (function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) {
 
 // Protect against Object overwritten by the user code.
 var Object = {}.constructor;
 
-/**
- * @constructor
- */
+function toString(obj)
+{
+    return String(obj);
+}
+
+function toStringDescription(obj)
+{
+    if (obj === 0 && 1 / obj < 0)
+        return "-0";
+
+    return toString(obj);
+}
+
+function isUInt32(obj)
+{
+    if (typeof obj === "number")
+        return obj >>> 0 === obj && (obj > 0 || 1 / obj > 0);
+    return "" + (obj >>> 0) === obj;
+}
+
+function isSymbol(obj)
+{
+    return typeof obj === "symbol";
+}
+
 var InjectedScript = function()
 {
     this._lastBoundObjectId = 1;
@@ -48,37 +66,30 @@ var InjectedScript = function()
     this._idToObjectGroupName = {};
     this._objectGroups = {};
     this._modules = {};
+    this._nextSavedResultIndex = 1;
+    this._savedResults = [];
 }
 
-/**
- * @type {Object.<string, boolean>}
- * @const
- */
 InjectedScript.primitiveTypes = {
     undefined: true,
     boolean: true,
     number: true,
-    string: true
+    string: true,
+}
+
+InjectedScript.CollectionMode = {
+    OwnProperties: 1 << 0,          // own properties.
+    NativeGetterProperties: 1 << 1, // native getter properties in the prototype chain.
+    AllProperties: 1 << 2,          // all properties in the prototype chain.
 }
 
 InjectedScript.prototype = {
-    /**
-     * @param {*} object
-     * @return {boolean}
-     */
     isPrimitiveValue: function(object)
     {
         // FIXME(33716): typeof document.all is always 'undefined'.
         return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
     },
 
-    /**
-     * @param {*} object
-     * @param {string} groupName
-     * @param {boolean} canAccessInspectedGlobalObject
-     * @param {boolean} generatePreview
-     * @return {!RuntimeAgent.RemoteObject}
-     */
     wrapObject: function(object, groupName, canAccessInspectedGlobalObject, generatePreview)
     {
         if (canAccessInspectedGlobalObject)
@@ -86,10 +97,16 @@ InjectedScript.prototype = {
         return this._fallbackWrapper(object);
     },
 
-    /**
-     * @param {*} object
-     * @return {!RuntimeAgent.RemoteObject}
-     */
+    setExceptionValue: function(value)
+    {
+        this._exceptionValue = value;
+    },
+
+    clearExceptionValue: function()
+    {
+        delete this._exceptionValue;
+    },
+
     _fallbackWrapper: function(object)
     {
         var result = {};
@@ -97,50 +114,38 @@ InjectedScript.prototype = {
         if (this.isPrimitiveValue(object))
             result.value = object;
         else
-            result.description = this._toString(object);
-        return /** @type {!RuntimeAgent.RemoteObject} */ (result);
+            result.description = toString(object);
+        return result;
     },
 
-    /**
-     * @param {boolean} canAccessInspectedGlobalObject
-     * @param {Object} table
-     * @param {Array.<string>|string|boolean} columns
-     * @return {!RuntimeAgent.RemoteObject}
-     */
     wrapTable: function(canAccessInspectedGlobalObject, table, columns)
     {
         if (!canAccessInspectedGlobalObject)
             return this._fallbackWrapper(table);
+
+        // FIXME: Currently columns are ignored. Instead, the frontend filters all
+        // properties based on the provided column names and in the provided order.
+        // Should we filter here too?
+
         var columnNames = null;
         if (typeof columns === "string")
             columns = [columns];
-        if (InjectedScriptHost.type(columns) == "array") {
+
+        if (InjectedScriptHost.subtype(columns) === "array") {
             columnNames = [];
             for (var i = 0; i < columns.length; ++i)
-                columnNames.push(String(columns[i]));
+                columnNames.push(toString(columns[i]));
         }
+
         return this._wrapObject(table, "console", false, true, columnNames);
     },
 
-    /**
-     * @param {*} object
-     */
     inspectObject: function(object)
     {
         if (this._commandLineAPIImpl)
             this._commandLineAPIImpl.inspect(object);
     },
 
-    /**
-     * This method cannot throw.
-     * @param {*} object
-     * @param {string=} objectGroupName
-     * @param {boolean=} forceValueType
-     * @param {boolean=} generatePreview
-     * @param {?Array.<string>=} columnNames
-     * @return {!RuntimeAgent.RemoteObject}
-     * @suppress {checkTypes}
-     */
     _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames)
     {
         try {
@@ -155,11 +160,6 @@ InjectedScript.prototype = {
         }
     },
 
-    /**
-     * @param {*} object
-     * @param {string=} objectGroupName
-     * @return {string}
-     */
     _bind: function(object, objectGroupName)
     {
         var id = this._lastBoundObjectId++;
@@ -177,33 +177,29 @@ InjectedScript.prototype = {
         return objectId;
     },
 
-    /**
-     * @param {string} objectId
-     * @return {Object}
-     */
     _parseObjectId: function(objectId)
     {
         return InjectedScriptHost.evaluate("(" + objectId + ")");
     },
 
-    /**
-     * @param {string} objectGroupName
-     */
     releaseObjectGroup: function(objectGroupName)
     {
+        if (objectGroupName === "console") {
+            delete this._lastResult;
+            this._nextSavedResultIndex = 1;
+            this._savedResults = [];
+        }
+
         var group = this._objectGroups[objectGroupName];
         if (!group)
             return;
+
         for (var i = 0; i < group.length; i++)
             this._releaseObject(group[i]);
+
         delete this._objectGroups[objectGroupName];
     },
 
-    /**
-     * @param {string} methodName
-     * @param {string} args
-     * @return {*}
-     */
     dispatch: function(methodName, args)
     {
         var argsArray = InjectedScriptHost.evaluate("(" + args + ")");
@@ -216,12 +212,7 @@ InjectedScript.prototype = {
         return result;
     },
 
-    /**
-     * @param {string} objectId
-     * @param {boolean} ownProperties
-     * @return {Array.<RuntimeAgent.PropertyDescriptor>|boolean}
-     */
-    getProperties: function(objectId, ownProperties)
+    _getProperties: function(objectId, collectionMode, generatePreview)
     {
         var parsedObjectId = this._parseObjectId(objectId);
         var object = this._objectForId(parsedObjectId);
@@ -229,7 +220,11 @@ InjectedScript.prototype = {
 
         if (!this._isDefined(object))
             return false;
-        var descriptors = this._propertyDescriptors(object, ownProperties);
+
+        if (isSymbol(object))
+            return false;
+
+        var descriptors = this._propertyDescriptors(object, collectionMode);
 
         // Go over properties, wrap object values.
         for (var i = 0; i < descriptors.length; ++i) {
@@ -239,45 +234,90 @@ InjectedScript.prototype = {
             if ("set" in descriptor)
                 descriptor.set = this._wrapObject(descriptor.set, objectGroupName);
             if ("value" in descriptor)
-                descriptor.value = this._wrapObject(descriptor.value, objectGroupName);
+                descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
             if (!("configurable" in descriptor))
                 descriptor.configurable = false;
             if (!("enumerable" in descriptor))
                 descriptor.enumerable = false;
+            if ("symbol" in descriptor)
+                descriptor.symbol = this._wrapObject(descriptor.symbol, objectGroupName);
         }
+
         return descriptors;
     },
 
-    /**
-     * @param {string} objectId
-     * @return {Array.<Object>|boolean}
-     */
-    getInternalProperties: function(objectId, ownProperties)
+    getProperties: function(objectId, ownProperties, generatePreview)
+    {
+        var collectionMode = ownProperties ? InjectedScript.CollectionMode.OwnProperties : InjectedScript.CollectionMode.AllProperties;
+        return this._getProperties(objectId, collectionMode, generatePreview);
+    },
+
+    getDisplayableProperties: function(objectId, generatePreview)
+    {
+        var collectionMode = InjectedScript.CollectionMode.OwnProperties | InjectedScript.CollectionMode.NativeGetterProperties;
+        return this._getProperties(objectId, collectionMode, generatePreview);
+    },
+
+    getInternalProperties: function(objectId, generatePreview)
     {
         var parsedObjectId = this._parseObjectId(objectId);
         var object = this._objectForId(parsedObjectId);
         var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
+
         if (!this._isDefined(object))
             return false;
-        var descriptors = [];
-        var internalProperties = InjectedScriptHost.getInternalProperties(object);
-        if (internalProperties) {
-            for (var i = 0; i < internalProperties.length; i++) {
-                var property = internalProperties[i];
-                var descriptor = {
-                    name: property.name,
-                    value: this._wrapObject(property.value, objectGroupName)
-                };
-                descriptors.push(descriptor);
-            }
+
+        if (isSymbol(object))
+            return false;
+
+        var descriptors = this._internalPropertyDescriptors(object);
+        if (!descriptors)
+            return [];
+
+        // Go over properties, wrap object values.
+        for (var i = 0; i < descriptors.length; ++i) {
+            var descriptor = descriptors[i];
+            if ("value" in descriptor)
+                descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
         }
+
         return descriptors;
     },
 
-    /**
-     * @param {string} functionId
-     * @return {!DebuggerAgent.FunctionDetails|string}
-     */
+    getCollectionEntries: function(objectId, objectGroupName, startIndex, numberToFetch)
+    {
+        var parsedObjectId = this._parseObjectId(objectId);
+        var object = this._objectForId(parsedObjectId);
+        var objectGroupName = objectGroupName || this._idToObjectGroupName[parsedObjectId.id];
+
+        if (!this._isDefined(object))
+            return;
+
+        if (typeof object !== "object")
+            return;
+
+        var entries = this._entries(object, InjectedScriptHost.subtype(object), startIndex, numberToFetch);
+        return entries.map(function(entry) {
+            entry.value = injectedScript._wrapObject(entry.value, objectGroupName, false, true);
+            if ("key" in entry)
+                entry.key = injectedScript._wrapObject(entry.key, objectGroupName, false, true);
+            return entry;
+        });
+    },
+
+    saveResult: function(callArgumentJSON)
+    {
+        this._savedResultIndex = 0;
+
+        try {
+            var callArgument = InjectedScriptHost.evaluate("(" + callArgumentJSON + ")");
+            var value = this._resolveCallArgument(callArgument);
+            this._saveResult(value);
+        } catch (e) {}
+
+        return this._savedResultIndex;
+    },
+
     getFunctionDetails: function(functionId)
     {
         var parsedFunctionId = this._parseObjectId(functionId);
@@ -299,108 +339,24 @@ InjectedScript.prototype = {
         return details;
     },
 
-    /**
-     * @param {string} objectId
-     */
     releaseObject: function(objectId)
     {
         var parsedObjectId = this._parseObjectId(objectId);
         this._releaseObject(parsedObjectId.id);
     },
 
-    /**
-     * @param {string} id
-     */
     _releaseObject: function(id)
     {
         delete this._idToWrappedObject[id];
         delete this._idToObjectGroupName[id];
     },
 
-    /**
-     * @param {Object} object
-     * @param {boolean} ownProperties
-     * @return {Array.<Object>}
-     */
-    _propertyDescriptors: function(object, ownProperties)
+    evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
     {
-        var descriptors = [];
-        var nameProcessed = {};
-        nameProcessed["__proto__"] = null;
-        for (var o = object; this._isDefined(o); o = o.__proto__) {
-            var names = Object.getOwnPropertyNames(/** @type {!Object} */ (o));
-            for (var i = 0; i < names.length; ++i) {
-                var name = names[i];
-                if (nameProcessed[name])
-                    continue;
-
-                try {
-                    nameProcessed[name] = true;
-                    var descriptor = Object.getOwnPropertyDescriptor(/** @type {!Object} */ (object), name);
-                    if (!descriptor) {
-                        // Not all bindings provide proper descriptors. Fall back to the writable, configurable property.
-                        try {
-                            descriptor = { name: name, value: object[name], writable: false, configurable: false, enumerable: false};
-                            if (o === object)
-                                descriptor.isOwn = true;
-                            descriptors.push(descriptor);
-                        } catch (e) {
-                            // Silent catch.
-                        }
-                        continue;
-                    }
-                    if (descriptor.hasOwnProperty("get") && descriptor.hasOwnProperty("set") && !descriptor.get && !descriptor.set) {
-                        // Not all bindings provide proper descriptors. Fall back to the writable, configurable property.
-                        try {
-                            descriptor = { name: name, value: object[name], writable: false, configurable: false, enumerable: false};
-                            if (o === object)
-                                descriptor.isOwn = true;
-                            descriptors.push(descriptor);
-                        } catch (e) {
-                            // Silent catch.
-                        }
-                        continue;
-                    }
-                } catch (e) {
-                    var descriptor = {};
-                    descriptor.value = e;
-                    descriptor.wasThrown = true;
-                }
-
-                descriptor.name = name;
-                if (o === object)
-                    descriptor.isOwn = true;
-                descriptors.push(descriptor);
-            }
-            if (ownProperties) {
-                if (object.__proto__)
-                    descriptors.push({ name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true});
-                break;
-            }
-        }
-        return descriptors;
+        return this._evaluateAndWrap(InjectedScriptHost.evaluate, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
     },
 
-    /**
-     * @param {string} expression
-     * @param {string} objectGroup
-     * @param {boolean} injectCommandLineAPI
-     * @param {boolean} returnByValue
-     * @param {boolean} generatePreview
-     * @return {*}
-     */
-    evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview)
-    {
-        return this._evaluateAndWrap(InjectedScriptHost.evaluate, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview);
-    },
-
-    /**
-     * @param {string} objectId
-     * @param {string} expression
-     * @param {boolean} returnByValue
-     * @return {Object|string}
-     */
-    callFunctionOn: function(objectId, expression, args, returnByValue)
+    callFunctionOn: function(objectId, expression, args, returnByValue, generatePreview)
     {
         var parsedObjectId = this._parseObjectId(objectId);
         var object = this._objectForId(parsedObjectId);
@@ -409,15 +365,13 @@ InjectedScript.prototype = {
 
         if (args) {
             var resolvedArgs = [];
-            args = InjectedScriptHost.evaluate(args);
-            for (var i = 0; i < args.length; ++i) {
-                var resolvedCallArgument;
+            var callArgs = InjectedScriptHost.evaluate(args);
+            for (var i = 0; i < callArgs.length; ++i) {
                 try {
-                    resolvedCallArgument = this._resolveCallArgument(args[i]);
+                    resolvedArgs[i] = this._resolveCallArgument(callArgs[i]);
                 } catch (e) {
                     return String(e);
                 }
-                resolvedArgs.push(resolvedCallArgument)
             }
         }
 
@@ -427,21 +381,21 @@ InjectedScript.prototype = {
             if (typeof func !== "function")
                 return "Given expression does not evaluate to a function";
 
-            return { wasThrown: false,
-                     result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue) };
+            return {
+                wasThrown: false,
+                result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue, generatePreview)
+            };
         } catch (e) {
             return this._createThrownValue(e, objectGroup);
         }
     },
 
-    /**
-     * Resolves a value from CallArgument description.
-     * @param {RuntimeAgent.CallArgument} callArgumentJson
-     * @return {*} resolved value
-     * @throws {string} error message
-     */
-    _resolveCallArgument: function(callArgumentJson) {
-        var objectId = callArgumentJson.objectId;
+    _resolveCallArgument: function(callArgumentJSON)
+    {
+        if ("value" in callArgumentJSON)
+            return callArgumentJSON.value;
+
+        var objectId = callArgumentJSON.objectId;
         if (objectId) {
             var parsedArgId = this._parseObjectId(objectId);
             if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId)
@@ -452,57 +406,43 @@ InjectedScript.prototype = {
                 throw "Could not find object with given id";
 
             return resolvedArg;
-        } else if ("value" in callArgumentJson)
-            return callArgumentJson.value;
-        else
-            return undefined;
+        }
+
+        return undefined;
     },
 
-    /**
-     * @param {Function} evalFunction
-     * @param {Object} object
-     * @param {string} objectGroup
-     * @param {boolean} isEvalOnCallFrame
-     * @param {boolean} injectCommandLineAPI
-     * @param {boolean} returnByValue
-     * @param {boolean} generatePreview
-     * @return {*}
-     */
-    _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview)
+    _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
     {
         try {
-            return { wasThrown: false,
-                     result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI), objectGroup, returnByValue, generatePreview) };
+            this._savedResultIndex = 0;
+
+            var returnObject = {
+                wasThrown: false,
+                result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult), objectGroup, returnByValue, generatePreview)
+            };
+
+            if (saveResult && this._savedResultIndex)
+                returnObject.savedResultIndex = this._savedResultIndex;
+
+            return returnObject;
         } catch (e) {
             return this._createThrownValue(e, objectGroup);
         }
     },
 
-    /**
-     * @param {*} value
-     * @param {string} objectGroup
-     * @return {Object}
-     */
     _createThrownValue: function(value, objectGroup)
     {
         var remoteObject = this._wrapObject(value, objectGroup);
         try {
-            remoteObject.description = this._toString(value);
+            remoteObject.description = toStringDescription(value);
         } catch (e) {}
-        return { wasThrown: true,
-                 result: remoteObject };
+        return {
+            wasThrown: true,
+            result: remoteObject
+        };
     },
 
-    /**
-     * @param {Function} evalFunction
-     * @param {Object} object
-     * @param {string} objectGroup
-     * @param {string} expression
-     * @param {boolean} isEvalOnCallFrame
-     * @param {boolean} injectCommandLineAPI
-     * @return {*}
-     */
-    _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI)
+    _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult)
     {
         var commandLineAPI = null;
         if (injectCommandLineAPI) {
@@ -547,8 +487,8 @@ InjectedScript.prototype = {
             var expressionFunction = evalFunction.call(object, boundExpressionFunctionString);
             var result = expressionFunction.apply(null, parameters);
 
-            if (objectGroup === "console")
-                this._lastResult = result;
+            if (objectGroup === "console" && saveResult)
+                this._saveResult(result);
 
             return result;
         }
@@ -567,8 +507,8 @@ InjectedScript.prototype = {
 
             var result = evalFunction.call(inspectedGlobalObject, expression);
 
-            if (objectGroup === "console")
-                this._lastResult = result;
+            if (objectGroup === "console" && saveResult)
+                this._saveResult(result);
 
             return result;
         } finally {
@@ -581,10 +521,6 @@ InjectedScript.prototype = {
         }
     },
 
-    /**
-     * @param {Object} callFrame
-     * @return {Array.<InjectedScript.CallFrameProxy>|boolean}
-     */
     wrapCallFrames: function(callFrame)
     {
         if (!callFrame)
@@ -599,29 +535,14 @@ InjectedScript.prototype = {
         return result;
     },
 
-    /**
-     * @param {Object} topCallFrame
-     * @param {string} callFrameId
-     * @param {string} expression
-     * @param {string} objectGroup
-     * @param {boolean} injectCommandLineAPI
-     * @param {boolean} returnByValue
-     * @param {boolean} generatePreview
-     * @return {*}
-     */
-    evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview)
+    evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
     {
         var callFrame = this._callFrameForId(topCallFrame, callFrameId);
         if (!callFrame)
             return "Could not find call frame with given id";
-        return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview);
+        return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
     },
 
-    /**
-     * @param {Object} topCallFrame
-     * @param {string} callFrameId
-     * @return {Object}
-     */
     _callFrameForId: function(topCallFrame, callFrameId)
     {
         var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")");
@@ -632,88 +553,215 @@ InjectedScript.prototype = {
         return callFrame;
     },
 
-    /**
-     * @param {Object} objectId
-     * @return {Object}
-     */
     _objectForId: function(objectId)
     {
         return this._idToWrappedObject[objectId.id];
     },
 
-    /**
-     * @param {string} objectId
-     * @return {Object}
-     */
     findObjectById: function(objectId)
     {
         var parsedObjectId = this._parseObjectId(objectId);
         return this._objectForId(parsedObjectId);
     },
 
-    /**
-     * @param {string} name
-     * @return {Object}
-     */
     module: function(name)
     {
         return this._modules[name];
     },
 
-    /**
-     * @param {string} name
-     * @param {string} source
-     * @return {Object}
-     */
     injectModule: function(name, source, host)
     {
         delete this._modules[name];
+
         var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")");
         if (typeof moduleFunction !== "function") {
             if (inspectedGlobalObject.console)
                 inspectedGlobalObject.console.error("Web Inspector error: A function was expected for module %s evaluation", name);
             return null;
         }
+
         var module = moduleFunction.call(inspectedGlobalObject, InjectedScriptHost, inspectedGlobalObject, injectedScriptId, this, host);
         this._modules[name] = module;
         return module;
     },
 
-    /**
-     * @param {*} object
-     * @return {boolean}
-     */
+    _internalPropertyDescriptors: function(object, completeDescriptor)
+    {
+        var internalProperties = InjectedScriptHost.getInternalProperties(object);
+        if (!internalProperties)
+            return null;
+
+        var descriptors = [];
+        for (var i = 0; i < internalProperties.length; i++) {
+            var property = internalProperties[i];
+            var descriptor = {name: property.name, value: property.value};
+            if (completeDescriptor) {
+                descriptor.writable = false;
+                descriptor.configurable = false;
+                descriptor.enumerable = false;
+                descriptor.isOwn = true;
+            }
+            descriptors.push(descriptor);
+        }
+        return descriptors;
+    },
+
+    _propertyDescriptors: function(object, collectionMode)
+    {
+        var descriptors = [];
+        var nameProcessed = new Set;
+
+        function createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty, possibleNativeBindingGetter)
+        {
+            try {
+                var descriptor = {name, value: object[name], writable: descriptor.writable || false, configurable: descriptor.configurable || false, enumerable: descriptor.enumerable || false};
+                if (possibleNativeBindingGetter)
+                    descriptor.nativeGetter = true;
+                if (isOwnProperty)
+                    descriptor.isOwn = true;
+                if (symbol)
+                    descriptor.symbol = symbol;
+                return descriptor;
+            } catch (e) {
+                var errorDescriptor = {name, value: e, wasThrown: true};
+                if (isOwnProperty)
+                    errorDescriptor.isOwn = true;
+                if (symbol)
+                    errorDescriptor.symbol = symbol;
+                return errorDescriptor;
+            }
+        }
+
+        function processDescriptor(descriptor, isOwnProperty, possibleNativeBindingGetter)
+        {
+            // All properties.
+            if (collectionMode & InjectedScript.CollectionMode.AllProperties) {
+                descriptors.push(descriptor);
+                return;
+            }
+
+            // Own properties.
+            if (collectionMode & InjectedScript.CollectionMode.OwnProperties && isOwnProperty) {
+                descriptors.push(descriptor);
+                return;
+            }
+
+            // Native Getter properties.
+            if (collectionMode & InjectedScript.CollectionMode.NativeGetterProperties) {
+                // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete
+                // if (descriptor.hasOwnProperty("get") && descriptor.get && isNativeFunction(descriptor.get)) { ... }
+
+                if (possibleNativeBindingGetter) {
+                    // Possible getter property in the prototype chain.
+                    descriptors.push(descriptor);
+                    return;
+                }
+            }
+        }
+
+        function processProperties(o, properties, isOwnProperty)
+        {
+            for (var i = 0; i < properties.length; ++i) {
+                var property = properties[i];
+                if (nameProcessed.has(property) || property === "__proto__")
+                    continue;
+
+                nameProcessed.add(property);
+
+                var name = toString(property);
+                var symbol = isSymbol(property) ? property : null;
+
+                var descriptor = Object.getOwnPropertyDescriptor(o, property);
+                if (!descriptor) {
+                    // FIXME: Bad descriptor. Can we get here?
+                    // Fall back to very restrictive settings.
+                    var fakeDescriptor = createFakeValueDescriptor(name, symbol, {writable: false, configurable: false, enumerable: false}, isOwnProperty);
+                    processDescriptor(fakeDescriptor, isOwnProperty);
+                    continue;
+                }
+
+                if (descriptor.hasOwnProperty("get") && descriptor.hasOwnProperty("set") && !descriptor.get && !descriptor.set) {
+                    // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete
+                    // Developers may create such a descriptors, so we should be resilient:
+                    // var x = {}; Object.defineProperty(x, "p", {get:undefined}); Object.getOwnPropertyDescriptor(x, "p")
+                    var fakeDescriptor = createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty, true);
+                    processDescriptor(fakeDescriptor, isOwnProperty, true);
+                    continue;
+                }
+
+                descriptor.name = name;
+                if (isOwnProperty)
+                    descriptor.isOwn = true;
+                if (symbol)
+                    descriptor.symbol = symbol;
+                processDescriptor(descriptor, isOwnProperty);
+            }
+        }
+
+        function arrayIndexPropertyNames(o, length)
+        {
+            var array = new Array(length);
+            for (var i = 0; i < length; ++i) {
+                if (i in o)
+                    array.push("" + i);
+            }
+            return array;
+        }
+
+        // FIXME: <https://webkit.org/b/143589> Web Inspector: Better handling for large collections in Object Trees
+        // For array types with a large length we attempt to skip getOwnPropertyNames and instead just sublist of indexes.
+        var isArrayTypeWithLargeLength = false;
+        try {
+            isArrayTypeWithLargeLength = injectedScript._subtype(object) === "array" && isFinite(object.length) && object.length > 100;
+        } catch(e) {}
+
+        for (var o = object; this._isDefined(o); o = o.__proto__) {
+            var isOwnProperty = o === object;
+
+            if (isArrayTypeWithLargeLength && isOwnProperty)
+                processProperties(o, arrayIndexPropertyNames(o, 100), isOwnProperty);
+            else {
+                processProperties(o, Object.getOwnPropertyNames(o), isOwnProperty);
+                if (Object.getOwnPropertySymbols)
+                    processProperties(o, Object.getOwnPropertySymbols(o), isOwnProperty);
+            }
+
+            if (collectionMode === InjectedScript.CollectionMode.OwnProperties)
+                break;
+        }
+
+        // Always include __proto__ at the end.
+        try {
+            if (object.__proto__)
+                descriptors.push({name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true});
+        } catch (e) {}
+
+        return descriptors;
+    },
+
     _isDefined: function(object)
     {
         return !!object || this._isHTMLAllCollection(object);
     },
 
-    /**
-     * @param {*} object
-     * @return {boolean}
-     */
     _isHTMLAllCollection: function(object)
     {
         // document.all is reported as undefined, but we still want to process it.
         return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object);
     },
 
-    /**
-     * @param {Object=} obj
-     * @return {string?}
-     */
     _subtype: function(obj)
     {
         if (obj === null)
             return "null";
 
-        if (this.isPrimitiveValue(obj))
+        if (this.isPrimitiveValue(obj) || isSymbol(obj))
             return null;
 
         if (this._isHTMLAllCollection(obj))
             return "array";
 
-        var preciseType = InjectedScriptHost.type(obj);
+        var preciseType = InjectedScriptHost.subtype(obj);
         if (preciseType)
             return preciseType;
 
@@ -721,321 +769,621 @@ InjectedScript.prototype = {
         try {
             if (typeof obj.splice === "function" && isFinite(obj.length))
                 return "array";
-            if (Object.prototype.toString.call(obj) === "[object Arguments]" && isFinite(obj.length)) // arguments.
-                return "array";
         } catch (e) {
         }
 
-        // If owning frame has navigated to somewhere else window properties will be undefined.
         return null;
     },
 
-    /**
-     * @param {*} obj
-     * @return {string?}
-     */
+    _nodeDescription: function(node)
+    {
+        var isXMLDocument = node.ownerDocument && !!node.ownerDocument.xmlVersion;
+        var description = isXMLDocument ? node.nodeName : node.nodeName.toLowerCase();
+
+        switch (node.nodeType) {
+        case 1: // Node.ELEMENT_NODE
+            if (node.id)
+                description += "#" + node.id;
+            if (node.hasAttribute("class")) {
+                // Using .getAttribute() is a workaround for SVG*Element.className returning SVGAnimatedString,
+                // which doesn't have any useful String methods. See <https://webkit.org/b/145363/>.
+                description += "." + node.getAttribute("class").trim().replace(/\s+/g, ".");
+            }
+            return description;
+
+        default:
+            return description;
+        }
+    },
+
+    _classPreview: function(classConstructorValue)
+    {
+        return "class " + classConstructorValue.name;
+    },
+
+    _nodePreview: function(node)
+    {
+        var isXMLDocument = node.ownerDocument && !!node.ownerDocument.xmlVersion;
+        var nodeName = isXMLDocument ? node.nodeName : node.nodeName.toLowerCase();
+
+        switch (node.nodeType) {
+        case 1: // Node.ELEMENT_NODE
+            if (node.id)
+                return "<" + nodeName + " id=\"" + node.id + "\">";
+            if (node.className)
+                return "<" + nodeName + " class=\"" + node.className + "\">";
+            if (nodeName === "input" && node.type)
+                return "<" + nodeName + " type=\"" + node.type + "\">";
+            return "<" + nodeName + ">";
+
+        case 3: // Node.TEXT_NODE
+            return nodeName + " \"" + node.nodeValue + "\"";
+
+        case 8: // Node.COMMENT_NODE
+            return "<!--" + node.nodeValue + "-->";
+
+        case 10: // Node.DOCUMENT_TYPE_NODE
+            return "<!DOCTYPE " + nodeName + ">";
+
+        default:
+            return nodeName;
+        }
+    },
+
     _describe: function(obj)
     {
         if (this.isPrimitiveValue(obj))
             return null;
 
-        obj = /** @type {Object} */ (obj);
+        if (isSymbol(obj))
+            return toString(obj);
 
-        // Type is object, get subtype.
         var subtype = this._subtype(obj);
 
         if (subtype === "regexp")
-            return this._toString(obj);
+            return toString(obj);
 
         if (subtype === "date")
-            return this._toString(obj);
-
-        if (subtype === "node") {
-            var description = obj.nodeName.toLowerCase();
-            switch (obj.nodeType) {
-            case 1 /* Node.ELEMENT_NODE */:
-                description += obj.id ? "#" + obj.id : "";
-                var className = obj.className;
-                description += className ? "." + className : "";
-                break;
-            case 10 /*Node.DOCUMENT_TYPE_NODE */:
-                description = "<!DOCTYPE " + description + ">";
-                break;
-            }
-            return description;
-        }
+            return toString(obj);
+
+        if (subtype === "error")
+            return toString(obj);
+
+        if (subtype === "node")
+            return this._nodeDescription(obj);
 
         var className = InjectedScriptHost.internalConstructorName(obj);
-        if (subtype === "array") {
-            if (typeof obj.length === "number")
-                className += "[" + obj.length + "]";
+        if (subtype === "array")
             return className;
-        }
 
         // NodeList in JSC is a function, check for array prior to this.
         if (typeof obj === "function")
-            return this._toString(obj);
+            return toString(obj);
 
+        // If Object, try for a better name from the constructor.
         if (className === "Object") {
-            // In Chromium DOM wrapper prototypes will have Object as their constructor name,
-            // get the real DOM wrapper name from the constructor property.
             var constructorName = obj.constructor && obj.constructor.name;
             if (constructorName)
                 return constructorName;
         }
+
         return className;
     },
 
-    /**
-     * @param {*} obj
-     * @return {string}
-     */
-    _toString: function(obj)
+    _getSetEntries: function(object, skip, numberToFetch)
+    {
+        var entries = [];
+
+        for (var value of object) {
+            if (skip > 0) {
+                skip--;
+                continue;
+            }
+
+            entries.push({value});
+
+            if (numberToFetch && entries.length === numberToFetch)
+                break;
+        }
+
+        return entries;
+    },
+
+    _getMapEntries: function(object, skip, numberToFetch)
+    {
+        var entries = [];
+
+        for (var [key, value] of object) {
+            if (skip > 0) {
+                skip--;
+                continue;
+            }
+
+            entries.push({key, value});
+
+            if (numberToFetch && entries.length === numberToFetch)
+                break;
+        }
+
+        return entries;
+    },
+
+    _getWeakMapEntries: function(object, numberToFetch)
+    {
+        return InjectedScriptHost.weakMapEntries(object, numberToFetch);
+    },
+
+    _getWeakSetEntries: function(object, numberToFetch)
     {
-        // We don't use String(obj) because inspectedGlobalObject.String is undefined if owning frame navigated to another page.
-        return "" + obj;
+        return InjectedScriptHost.weakSetEntries(object, numberToFetch);
+    },
+
+    _getIteratorEntries: function(object, numberToFetch)
+    {
+        return InjectedScriptHost.iteratorEntries(object, numberToFetch);
+    },
+
+    _entries: function(object, subtype, startIndex, numberToFetch)
+    {
+        if (subtype === "set")
+            return this._getSetEntries(object, startIndex, numberToFetch);
+        if (subtype === "map")
+            return this._getMapEntries(object, startIndex, numberToFetch);
+        if (subtype === "weakmap")
+            return this._getWeakMapEntries(object, numberToFetch);
+        if (subtype === "weakset")
+            return this._getWeakSetEntries(object, numberToFetch);
+        if (subtype === "iterator")
+            return this._getIteratorEntries(object, numberToFetch);
+
+        throw "unexpected type";
+    },
+
+    _saveResult: function(result)
+    {
+        this._lastResult = result;
+
+        if (result === undefined || result === null)
+            return;
+
+        var existingIndex = this._savedResults.indexOf(result);
+        if (existingIndex !== -1) {
+            this._savedResultIndex = existingIndex;
+            return;
+        }
+
+        this._savedResultIndex = this._nextSavedResultIndex;
+        this._savedResults[this._nextSavedResultIndex++] = result;
+
+        // $n is limited from $1-$99. $0 is special.
+        if (this._nextSavedResultIndex >= 100)
+            this._nextSavedResultIndex = 1;
+    },
+
+    _savedResult: function(index)
+    {
+        return this._savedResults[index];
     }
 }
 
-/**
- * @type {InjectedScript}
- * @const
- */
-var injectedScript = new InjectedScript();
-
-/**
- * @constructor
- * @param {*} object
- * @param {string=} objectGroupName
- * @param {boolean=} forceValueType
- * @param {boolean=} generatePreview
- * @param {?Array.<string>=} columnNames
- */
+var injectedScript = new InjectedScript;
+
+
 InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames)
 {
     this.type = typeof object;
+
+    if (this.type === "undefined" && injectedScript._isHTMLAllCollection(object))
+        this.type = "object";
+
     if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
         // We don't send undefined values over JSON.
-        if (typeof object !== "undefined")
+        if (this.type !== "undefined")
             this.value = object;
 
-        // Null object is object with 'null' subtype'
+        // Null object is object with 'null' subtype.
         if (object === null)
             this.subtype = "null";
 
         // Provide user-friendly number values.
-        if (typeof object === "number")
-            this.description = object + "";
+        if (this.type === "number")
+            this.description = toStringDescription(object);
         return;
     }
 
-    object = /** @type {Object} */ (object);
-
     this.objectId = injectedScript._bind(object, objectGroupName);
+
     var subtype = injectedScript._subtype(object);
     if (subtype)
         this.subtype = subtype;
+
     this.className = InjectedScriptHost.internalConstructorName(object);
     this.description = injectedScript._describe(object);
 
-    if (generatePreview && (this.type === "object" || injectedScript._isHTMLAllCollection(object)))
+    if (subtype === "array")
+        this.size = typeof object.length === "number" ? object.length : 0;
+    else if (subtype === "set" || subtype === "map")
+        this.size = object.size;
+    else if (subtype === "weakmap")
+        this.size = InjectedScriptHost.weakMapSize(object);
+    else if (subtype === "weakset")
+        this.size = InjectedScriptHost.weakSetSize(object);
+    else if (subtype === "class") {
+        this.classPrototype = injectedScript._wrapObject(object.prototype, objectGroupName);
+        this.className = object.name;
+    }
+
+    if (generatePreview && this.type === "object")
         this.preview = this._generatePreview(object, undefined, columnNames);
 }
 
 InjectedScript.RemoteObject.prototype = {
-    /**
-     * @param {Object} object
-     * @param {Array.<string>=} firstLevelKeys
-     * @param {?Array.<string>=} secondLevelKeys
-     * @return {Object} preview
-     */
+    _emptyPreview: function()
+    {
+        var preview = {
+            type: this.type,
+            description: this.description || toString(this.value),
+            lossless: true,
+        };
+
+        if (this.subtype) {
+            preview.subtype = this.subtype;
+            if (this.subtype !== "null") {
+                preview.overflow = false;
+                preview.properties = [];
+            }
+        }
+
+        if ("size" in this)
+            preview.size = this.size;
+
+        return preview;
+    },
+
+    _createObjectPreviewForValue: function(value)
+    {
+        var remoteObject = new InjectedScript.RemoteObject(value, undefined, false, true, undefined);
+        if (remoteObject.objectId)
+            injectedScript.releaseObject(remoteObject.objectId);
+        if (remoteObject.classPrototype && remoteObject.classPrototype.objectId)
+            injectedScript.releaseObject(remoteObject.classPrototype.objectId);
+
+        return remoteObject.preview || remoteObject._emptyPreview();
+    },
+
     _generatePreview: function(object, firstLevelKeys, secondLevelKeys)
     {
-        var preview = {};
-        preview.lossless = true;
-        preview.overflow = false;
-        preview.properties = [];
+        var preview = this._emptyPreview();
+
+        // Primitives just have a value.
+        if (this.type !== "object")
+            return;
 
         var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys;
         var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
 
         var propertiesThreshold = {
             properties: isTableRowsRequest ? 1000 : Math.max(5, firstLevelKeysCount),
-            indexes: isTableRowsRequest ? 1000 : Math.max(100, firstLevelKeysCount)
+            indexes: isTableRowsRequest ? 1000 : Math.max(10, firstLevelKeysCount)
         };
-        for (var o = object; injectedScript._isDefined(o); o = o.__proto__)
-            this._generateProtoPreview(o, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys);
+
+        try {
+            // Maps, Sets, and Iterators have entries.
+            if (this.subtype === "map" || this.subtype === "set" || this.subtype === "weakmap" || this.subtype === "weakset" || this.subtype === "iterator")
+                this._appendEntryPreviews(object, preview);
+
+            preview.properties = [];
+
+            // Internal Properties.
+            var internalPropertyDescriptors = injectedScript._internalPropertyDescriptors(object, true);
+            if (internalPropertyDescriptors) {
+                this._appendPropertyPreviews(preview, internalPropertyDescriptors, true, propertiesThreshold, firstLevelKeys, secondLevelKeys);
+                if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
+                    return preview;
+            }
+
+            if (preview.entries)
+                return preview;
+
+            // Properties.
+            var descriptors = injectedScript._propertyDescriptors(object, InjectedScript.CollectionMode.AllProperties);
+            this._appendPropertyPreviews(preview, descriptors, false, propertiesThreshold, firstLevelKeys, secondLevelKeys);
+            if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
+                return preview;
+        } catch (e) {
+            preview.lossless = false;
+        }
+
         return preview;
     },
 
-    /**
-     * @param {Object} object
-     * @param {Object} preview
-     * @param {Object} propertiesThreshold
-     * @param {Array.<string>=} firstLevelKeys
-     * @param {Array.<string>=} secondLevelKeys
-     */
-    _generateProtoPreview: function(object, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys)
+    _appendPropertyPreviews: function(preview, descriptors, internal, propertiesThreshold, firstLevelKeys, secondLevelKeys)
     {
-        var propertyNames = firstLevelKeys ? firstLevelKeys : Object.keys(/** @type {!Object} */(object));
-        try {
-            for (var i = 0; i < propertyNames.length; ++i) {
-                if (!propertiesThreshold.properties || !propertiesThreshold.indexes) {
-                    preview.overflow = true;
+        for (var descriptor of descriptors) {
+            // Seen enough.
+            if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
+                break;
+
+            // Error in descriptor.
+            if (descriptor.wasThrown) {
+                preview.lossless = false;
+                continue;
+            }
+
+            // Do not show "__proto__" in preview.
+            var name = descriptor.name;
+            if (name === "__proto__")
+                continue;
+
+            // For arrays, only allow indexes.
+            if (this.subtype === "array" && !isUInt32(name))
+                continue;
+
+            // Do not show non-enumerable non-own properties. Special case to allow array indexes that may be on the prototype.
+            if (!descriptor.enumerable && !descriptor.isOwn && this.subtype !== "array")
+                continue;
+
+            // If we have a filter, only show properties in the filter.
+            // FIXME: Currently these filters do nothing on the backend.
+            if (firstLevelKeys && !firstLevelKeys.includes(name))
+                continue;
+
+            // Getter/setter.
+            if (!("value" in descriptor)) {
+                preview.lossless = false;
+                this._appendPropertyPreview(preview, internal, {name, type: "accessor"}, propertiesThreshold);
+                continue;
+            }
+
+            // Null value.
+            var value = descriptor.value;
+            if (value === null) {
+                this._appendPropertyPreview(preview, internal, {name, type: "object", subtype: "null", value: "null"}, propertiesThreshold);
+                continue;
+            }
+
+            // Ignore non-enumerable functions.
+            var type = typeof value;
+            if (!descriptor.enumerable && type === "function")
+                continue;
+
+            // Fix type of document.all.
+            if (type === "undefined" && injectedScript._isHTMLAllCollection(value))
+                type = "object";
+
+            // Primitive.
+            const maxLength = 100;
+            if (InjectedScript.primitiveTypes[type]) {
+                if (type === "string" && value.length > maxLength) {
+                    value = this._abbreviateString(value, maxLength, true);
                     preview.lossless = false;
-                    break;
                 }
-                var name = propertyNames[i];
-                if (this.subtype === "array" && name === "length")
-                    continue;
+                this._appendPropertyPreview(preview, internal, {name, type, value: toStringDescription(value)}, propertiesThreshold);
+                continue;
+            }
 
-                var descriptor = Object.getOwnPropertyDescriptor(/** @type {!Object} */(object), name);
-                if (!("value" in descriptor) || !descriptor.enumerable) {
+            // Symbol.
+            if (isSymbol(value)) {
+                var symbolString = toString(value);
+                if (symbolString.length > maxLength) {
+                    symbolString = this._abbreviateString(symbolString, maxLength, true);
                     preview.lossless = false;
-                    continue;
                 }
+                this._appendPropertyPreview(preview, internal, {name, type, value: symbolString}, propertiesThreshold);
+                return;
+            }
 
-                var value = descriptor.value;
-                if (value === null) {
-                    this._appendPropertyPreview(preview, { name: name, type: "object", value: "null" }, propertiesThreshold);
-                    continue;
+            // Object.
+            var property = {name, type};
+            var subtype = injectedScript._subtype(value);
+            if (subtype)
+                property.subtype = subtype;
+
+            // Second level.
+            if ((secondLevelKeys === null || secondLevelKeys) || this._isPreviewableObject(value)) {
+                // FIXME: If we want secondLevelKeys filter to continue we would need some refactoring.
+                var subPreview = this._createObjectPreviewForValue(value);
+                property.valuePreview = subPreview;
+                if (!subPreview.lossless)
+                    preview.lossless = false;
+                if (subPreview.overflow)
+                    preview.overflow = true;
+            } else {
+                var description = "";
+                if (type !== "function" || subtype === "class") {
+                    var fullDescription;
+                    if (subtype === "class")
+                        fullDescription = "class " + value.name;
+                    else if (subtype === "node")
+                        fullDescription = injectedScript._nodePreview(value);
+                    else
+                        fullDescription = injectedScript._describe(value);
+                    description = this._abbreviateString(fullDescription, maxLength, subtype === "regexp");
                 }
+                property.value = description;
+                preview.lossless = false;
+            }
 
-                const maxLength = 100;
-                var type = typeof value;
-
-                if (InjectedScript.primitiveTypes[type]) {
-                    if (type === "string") {
-                        if (value.length > maxLength) {
-                            value = this._abbreviateString(value, maxLength, true);
-                            preview.lossless = false;
-                        }
-                        value = value.replace(/\n/g, "\u21B5");
-                    }
-                    this._appendPropertyPreview(preview, { name: name, type: type, value: value + "" }, propertiesThreshold);
-                    continue;
-                }
+            this._appendPropertyPreview(preview, internal, property, propertiesThreshold);
+        }
+    },
 
-                if (secondLevelKeys === null || secondLevelKeys) {
-                    var subPreview = this._generatePreview(value, secondLevelKeys || undefined);
-                    var property = { name: name, type: type, valuePreview: subPreview };
-                    this._appendPropertyPreview(preview, property, propertiesThreshold);
-                    if (!subPreview.lossless)
-                        preview.lossless = false;
-                    if (subPreview.overflow)
-                        preview.overflow = true;
-                    continue;
-                }
+    _appendPropertyPreview: function(preview, internal, property, propertiesThreshold)
+    {
+        if (toString(property.name >>> 0) === property.name)
+            propertiesThreshold.indexes--;
+        else
+            propertiesThreshold.properties--;
 
-                preview.lossless = false;
+        if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) {
+            preview.overflow = true;
+            preview.lossless = false;
+            return;
+        }
 
-                var subtype = injectedScript._subtype(value);
-                var description = "";
-                if (type !== "function")
-                    description = this._abbreviateString(/** @type {string} */ (injectedScript._describe(value)), maxLength, subtype === "regexp");
+        if (internal)
+            property.internal = true;
 
-                var property = { name: name, type: type, value: description };
-                if (subtype)
-                    property.subtype = subtype;
-                this._appendPropertyPreview(preview, property, propertiesThreshold);
-            }
-        } catch (e) {
+        preview.properties.push(property);
+    },
+
+    _appendEntryPreviews: function(object, preview)
+    {
+        // Fetch 6, but only return 5, so we can tell if we overflowed.
+        var entries = injectedScript._entries(object, this.subtype, 0, 6);
+        if (!entries)
+            return;
+
+        if (entries.length > 5) {
+            entries.pop();
+            preview.overflow = true;
+            preview.lossless = false;
         }
+
+        preview.entries = entries.map(function(entry) {
+            entry.value = this._createObjectPreviewForValue(entry.value);
+            if ("key" in entry)
+                entry.key = this._createObjectPreviewForValue(entry.key);
+            return entry;
+        }, this);
     },
 
-    /**
-     * @param {Object} preview
-     * @param {Object} property
-     * @param {Object} propertiesThreshold
-     */
-    _appendPropertyPreview: function(preview, property, propertiesThreshold)
+    _isPreviewableObject: function(object)
     {
-        if (isNaN(property.name))
-            propertiesThreshold.properties--;
-        else
-            propertiesThreshold.indexes--;
-        preview.properties.push(property);
+        return this._isPreviewableObjectInternal(object, new Set, 1);
+    },
+
+    _isPreviewableObjectInternal: function(object, knownObjects, depth)
+    {
+        // Deep object.
+        if (depth > 3)
+            return false;
+
+        // Primitive.
+        if (injectedScript.isPrimitiveValue(object) || isSymbol(object))
+            return true;
+
+        // Null.
+        if (object === null)
+            return true;
+
+        // Cyclic objects.
+        if (knownObjects.has(object))
+            return false;
+
+        ++depth;
+        knownObjects.add(object);
+
+        // Arrays are simple if they have 5 or less simple objects.
+        var subtype = injectedScript._subtype(object);
+        if (subtype === "array") {
+            var length = object.length;
+            if (length > 5)
+                return false;
+            for (var i = 0; i < length; ++i) {
+                if (!this._isPreviewableObjectInternal(object[i], knownObjects, depth))
+                    return false;
+            }
+            return true;
+        }
+
+        // Not a basic object.
+        if (object.__proto__ && object.__proto__.__proto__)
+            return false;
+
+        // Objects are simple if they have 3 or less simple properties.
+        var ownPropertyNames = Object.getOwnPropertyNames(object);
+        if (ownPropertyNames.length > 3)
+            return false;
+        for (var propertyName of ownPropertyNames) {
+            if (!this._isPreviewableObjectInternal(object[propertyName], knownObjects, depth))
+                return false;
+        }
+
+        return true;
     },
 
-    /**
-     * @param {string} string
-     * @param {number} maxLength
-     * @param {boolean=} middle
-     * @returns
-     */
     _abbreviateString: function(string, maxLength, middle)
     {
         if (string.length <= maxLength)
             return string;
+
         if (middle) {
             var leftHalf = maxLength >> 1;
             var rightHalf = maxLength - leftHalf - 1;
             return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf);
         }
+
         return string.substr(0, maxLength) + "\u2026";
     }
 }
-/**
- * @constructor
- * @param {number} ordinal
- * @param {Object} callFrame
- */
+
 InjectedScript.CallFrameProxy = function(ordinal, callFrame)
 {
     this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}";
     this.functionName = (callFrame.type === "function" ? callFrame.functionName : "");
-    this.location = { scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column };
+    this.location = {scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column};
     this.scopeChain = this._wrapScopeChain(callFrame);
     this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace");
 }
 
 InjectedScript.CallFrameProxy.prototype = {
-    /**
-     * @param {Object} callFrame
-     * @return {!Array.<DebuggerAgent.Scope>}
-     */
     _wrapScopeChain: function(callFrame)
     {
         var scopeChain = callFrame.scopeChain;
         var scopeChainProxy = [];
-        for (var i = 0; i < scopeChain.length; i++) {
-            var scope = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace");
-            scopeChainProxy.push(scope);
-        }
+        for (var i = 0; i < scopeChain.length; i++)
+            scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace");
         return scopeChainProxy;
     }
 }
 
-/**
- * @param {number} scopeTypeCode
- * @param {*} scopeObject
- * @param {string} groupId
- * @return {!DebuggerAgent.Scope}
- */
-InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId) {
-    const GLOBAL_SCOPE = 0;
-    const LOCAL_SCOPE = 1;
-    const WITH_SCOPE = 2;
-    const CLOSURE_SCOPE = 3;
-    const CATCH_SCOPE = 4;
-
-    /** @type {!Object.<number, string>} */
-    var scopeTypeNames = {};
-    scopeTypeNames[GLOBAL_SCOPE] = "global";
-    scopeTypeNames[LOCAL_SCOPE] = "local";
-    scopeTypeNames[WITH_SCOPE] = "with";
-    scopeTypeNames[CLOSURE_SCOPE] = "closure";
-    scopeTypeNames[CATCH_SCOPE] = "catch";
+InjectedScript.CallFrameProxy._scopeTypeNames = {
+    0: "global", // GLOBAL_SCOPE
+    1: "local", // LOCAL_SCOPE
+    2: "with", // WITH_SCOPE
+    3: "closure", // CLOSURE_SCOPE
+    4: "catch", // CATCH_SCOPE
+    5: "functionName", // FUNCTION_NAME_SCOPE
+}
 
+InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId)
+{
     return {
         object: injectedScript._wrapObject(scopeObject, groupId),
-        type: /** @type {DebuggerAgent.ScopeType} */ (scopeTypeNames[scopeTypeCode])
+        type: InjectedScript.CallFrameProxy._scopeTypeNames[scopeTypeCode]
     };
 }
 
+
+function slice(array, index)
+{
+    var result = [];
+    for (var i = index || 0; i < array.length; ++i)
+        result.push(array[i]);
+    return result;
+}
+
+function bind(func, thisObject, var_args)
+{
+    var args = slice(arguments, 2);
+    return function(var_args) {
+        return func.apply(thisObject, args.concat(slice(arguments)));
+    }
+}
+
 function BasicCommandLineAPI()
 {
     this.$_ = injectedScript._lastResult;
+    this.$exception = injectedScript._exceptionValue;
+
+    // $1-$99
+    for (var i = 1; i <= injectedScript._savedResults.length; ++i) {
+        var member = "$" + i;
+        if (member in inspectedGlobalObject)
+            continue;
+        this.__defineGetter__("$" + i, bind(injectedScript._savedResult, injectedScript, i));
+    }
 }
 
 return injectedScript;