Web Inspector: ES6: Provide a better view for Classes in the console
[WebKit-https.git] / Source / JavaScriptCore / inspector / InjectedScriptSource.js
1 /*
2  * Copyright (C) 2007, 2014 Apple Inc.  All rights reserved.
3  * Copyright (C) 2013 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 //# sourceURL=__WebInspectorInjectedScript__
31
32 (function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) {
33
34 // Protect against Object overwritten by the user code.
35 var Object = {}.constructor;
36
37 function toString(obj)
38 {
39     return String(obj);
40 }
41
42 function toStringDescription(obj)
43 {
44     if (obj === 0 && 1 / obj < 0)
45         return "-0";
46
47     return toString(obj);
48 }
49
50 function isUInt32(obj)
51 {
52     if (typeof obj === "number")
53         return obj >>> 0 === obj && (obj > 0 || 1 / obj > 0);
54     return "" + (obj >>> 0) === obj;
55 }
56
57 function isSymbol(obj)
58 {
59     return typeof obj === "symbol";
60 }
61
62 var InjectedScript = function()
63 {
64     this._lastBoundObjectId = 1;
65     this._idToWrappedObject = {};
66     this._idToObjectGroupName = {};
67     this._objectGroups = {};
68     this._modules = {};
69     this._nextSavedResultIndex = 1;
70     this._savedResults = [];
71 }
72
73 InjectedScript.primitiveTypes = {
74     undefined: true,
75     boolean: true,
76     number: true,
77     string: true,
78 }
79
80 InjectedScript.CollectionMode = {
81     OwnProperties: 1 << 0,          // own properties.
82     NativeGetterProperties: 1 << 1, // native getter properties in the prototype chain.
83     AllProperties: 1 << 2,          // all properties in the prototype chain.
84 }
85
86 InjectedScript.prototype = {
87     isPrimitiveValue: function(object)
88     {
89         // FIXME(33716): typeof document.all is always 'undefined'.
90         return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
91     },
92
93     wrapObject: function(object, groupName, canAccessInspectedGlobalObject, generatePreview)
94     {
95         if (canAccessInspectedGlobalObject)
96             return this._wrapObject(object, groupName, false, generatePreview);
97         return this._fallbackWrapper(object);
98     },
99
100     setExceptionValue: function(value)
101     {
102         this._exceptionValue = value;
103     },
104
105     clearExceptionValue: function()
106     {
107         delete this._exceptionValue;
108     },
109
110     _fallbackWrapper: function(object)
111     {
112         var result = {};
113         result.type = typeof object;
114         if (this.isPrimitiveValue(object))
115             result.value = object;
116         else
117             result.description = toString(object);
118         return result;
119     },
120
121     wrapTable: function(canAccessInspectedGlobalObject, table, columns)
122     {
123         if (!canAccessInspectedGlobalObject)
124             return this._fallbackWrapper(table);
125
126         // FIXME: Currently columns are ignored. Instead, the frontend filters all
127         // properties based on the provided column names and in the provided order.
128         // Should we filter here too?
129
130         var columnNames = null;
131         if (typeof columns === "string")
132             columns = [columns];
133
134         if (InjectedScriptHost.subtype(columns) === "array") {
135             columnNames = [];
136             for (var i = 0; i < columns.length; ++i)
137                 columnNames.push(toString(columns[i]));
138         }
139
140         return this._wrapObject(table, "console", false, true, columnNames);
141     },
142
143     inspectObject: function(object)
144     {
145         if (this._commandLineAPIImpl)
146             this._commandLineAPIImpl.inspect(object);
147     },
148
149     _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames)
150     {
151         try {
152             return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType, generatePreview, columnNames);
153         } catch (e) {
154             try {
155                 var description = injectedScript._describe(e);
156             } catch (ex) {
157                 var description = "<failed to convert exception to string>";
158             }
159             return new InjectedScript.RemoteObject(description);
160         }
161     },
162
163     _bind: function(object, objectGroupName)
164     {
165         var id = this._lastBoundObjectId++;
166         this._idToWrappedObject[id] = object;
167         var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}";
168         if (objectGroupName) {
169             var group = this._objectGroups[objectGroupName];
170             if (!group) {
171                 group = [];
172                 this._objectGroups[objectGroupName] = group;
173             }
174             group.push(id);
175             this._idToObjectGroupName[id] = objectGroupName;
176         }
177         return objectId;
178     },
179
180     _parseObjectId: function(objectId)
181     {
182         return InjectedScriptHost.evaluate("(" + objectId + ")");
183     },
184
185     releaseObjectGroup: function(objectGroupName)
186     {
187         if (objectGroupName === "console") {
188             delete this._lastResult;
189             this._nextSavedResultIndex = 1;
190             this._savedResults = [];
191         }
192
193         var group = this._objectGroups[objectGroupName];
194         if (!group)
195             return;
196
197         for (var i = 0; i < group.length; i++)
198             this._releaseObject(group[i]);
199
200         delete this._objectGroups[objectGroupName];
201     },
202
203     dispatch: function(methodName, args)
204     {
205         var argsArray = InjectedScriptHost.evaluate("(" + args + ")");
206         var result = this[methodName].apply(this, argsArray);
207         if (typeof result === "undefined") {
208             if (inspectedGlobalObject.console)
209                 inspectedGlobalObject.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
210             result = null;
211         }
212         return result;
213     },
214
215     _getProperties: function(objectId, collectionMode, generatePreview)
216     {
217         var parsedObjectId = this._parseObjectId(objectId);
218         var object = this._objectForId(parsedObjectId);
219         var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
220
221         if (!this._isDefined(object))
222             return false;
223
224         if (isSymbol(object))
225             return false;
226
227         var descriptors = this._propertyDescriptors(object, collectionMode);
228
229         // Go over properties, wrap object values.
230         for (var i = 0; i < descriptors.length; ++i) {
231             var descriptor = descriptors[i];
232             if ("get" in descriptor)
233                 descriptor.get = this._wrapObject(descriptor.get, objectGroupName);
234             if ("set" in descriptor)
235                 descriptor.set = this._wrapObject(descriptor.set, objectGroupName);
236             if ("value" in descriptor)
237                 descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
238             if (!("configurable" in descriptor))
239                 descriptor.configurable = false;
240             if (!("enumerable" in descriptor))
241                 descriptor.enumerable = false;
242         }
243
244         return descriptors;
245     },
246
247     getProperties: function(objectId, ownProperties, generatePreview)
248     {
249         var collectionMode = ownProperties ? InjectedScript.CollectionMode.OwnProperties : InjectedScript.CollectionMode.AllProperties;
250         return this._getProperties(objectId, collectionMode, generatePreview);
251     },
252
253     getDisplayableProperties: function(objectId, generatePreview)
254     {
255         var collectionMode = InjectedScript.CollectionMode.OwnProperties | InjectedScript.CollectionMode.NativeGetterProperties;
256         return this._getProperties(objectId, collectionMode, generatePreview);
257     },
258
259     getInternalProperties: function(objectId, generatePreview)
260     {
261         var parsedObjectId = this._parseObjectId(objectId);
262         var object = this._objectForId(parsedObjectId);
263         var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
264
265         if (!this._isDefined(object))
266             return false;
267
268         if (isSymbol(object))
269             return false;
270
271         var descriptors = this._internalPropertyDescriptors(object);
272         if (!descriptors)
273             return [];
274
275         // Go over properties, wrap object values.
276         for (var i = 0; i < descriptors.length; ++i) {
277             var descriptor = descriptors[i];
278             if ("value" in descriptor)
279                 descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
280         }
281
282         return descriptors;
283     },
284
285     getCollectionEntries: function(objectId, objectGroupName, startIndex, numberToFetch)
286     {
287         var parsedObjectId = this._parseObjectId(objectId);
288         var object = this._objectForId(parsedObjectId);
289         var objectGroupName = objectGroupName || this._idToObjectGroupName[parsedObjectId.id];
290
291         if (!this._isDefined(object))
292             return;
293
294         if (typeof object !== "object")
295             return;
296
297         var entries = this._entries(object, InjectedScriptHost.subtype(object), startIndex, numberToFetch);
298         return entries.map(function(entry) {
299             entry.value = injectedScript._wrapObject(entry.value, objectGroupName, false, true);
300             if ("key" in entry)
301                 entry.key = injectedScript._wrapObject(entry.key, objectGroupName, false, true);
302             return entry;
303         });
304     },
305
306     saveResult: function(callArgumentJSON)
307     {
308         this._savedResultIndex = 0;
309
310         try {
311             var callArgument = InjectedScriptHost.evaluate("(" + callArgumentJSON + ")");
312             var value = this._resolveCallArgument(callArgument);
313             this._saveResult(value);
314         } catch (e) {}
315
316         return this._savedResultIndex;
317     },
318
319     getFunctionDetails: function(functionId)
320     {
321         var parsedFunctionId = this._parseObjectId(functionId);
322         var func = this._objectForId(parsedFunctionId);
323         if (typeof func !== "function")
324             return "Cannot resolve function by id.";
325         var details = InjectedScriptHost.functionDetails(func);
326         if (!details)
327             return "Cannot resolve function details.";
328         if ("rawScopes" in details) {
329             var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id];
330             var rawScopes = details.rawScopes;
331             var scopes = [];
332             delete details.rawScopes;
333             for (var i = 0; i < rawScopes.length; i++)
334                 scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName));
335             details.scopeChain = scopes;
336         }
337         return details;
338     },
339
340     releaseObject: function(objectId)
341     {
342         var parsedObjectId = this._parseObjectId(objectId);
343         this._releaseObject(parsedObjectId.id);
344     },
345
346     _releaseObject: function(id)
347     {
348         delete this._idToWrappedObject[id];
349         delete this._idToObjectGroupName[id];
350     },
351
352     evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
353     {
354         return this._evaluateAndWrap(InjectedScriptHost.evaluate, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
355     },
356
357     callFunctionOn: function(objectId, expression, args, returnByValue, generatePreview)
358     {
359         var parsedObjectId = this._parseObjectId(objectId);
360         var object = this._objectForId(parsedObjectId);
361         if (!this._isDefined(object))
362             return "Could not find object with given id";
363
364         if (args) {
365             var resolvedArgs = [];
366             var callArgs = InjectedScriptHost.evaluate(args);
367             for (var i = 0; i < callArgs.length; ++i) {
368                 try {
369                     resolvedArgs[i] = this._resolveCallArgument(callArgs[i]);
370                 } catch (e) {
371                     return String(e);
372                 }
373             }
374         }
375
376         try {
377             var objectGroup = this._idToObjectGroupName[parsedObjectId.id];
378             var func = InjectedScriptHost.evaluate("(" + expression + ")");
379             if (typeof func !== "function")
380                 return "Given expression does not evaluate to a function";
381
382             return {
383                 wasThrown: false,
384                 result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue, generatePreview)
385             };
386         } catch (e) {
387             return this._createThrownValue(e, objectGroup);
388         }
389     },
390
391     _resolveCallArgument: function(callArgumentJSON)
392     {
393         if ("value" in callArgumentJSON)
394             return callArgumentJSON.value;
395
396         var objectId = callArgumentJSON.objectId;
397         if (objectId) {
398             var parsedArgId = this._parseObjectId(objectId);
399             if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId)
400                 throw "Arguments should belong to the same JavaScript world as the target object.";
401
402             var resolvedArg = this._objectForId(parsedArgId);
403             if (!this._isDefined(resolvedArg))
404                 throw "Could not find object with given id";
405
406             return resolvedArg;
407         }
408
409         return undefined;
410     },
411
412     _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
413     {
414         try {
415             this._savedResultIndex = 0;
416
417             var returnObject = {
418                 wasThrown: false,
419                 result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult), objectGroup, returnByValue, generatePreview)
420             };
421
422             if (saveResult && this._savedResultIndex)
423                 returnObject.savedResultIndex = this._savedResultIndex;
424
425             return returnObject;
426         } catch (e) {
427             return this._createThrownValue(e, objectGroup);
428         }
429     },
430
431     _createThrownValue: function(value, objectGroup)
432     {
433         var remoteObject = this._wrapObject(value, objectGroup);
434         try {
435             remoteObject.description = toStringDescription(value);
436         } catch (e) {}
437         return {
438             wasThrown: true,
439             result: remoteObject
440         };
441     },
442
443     _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult)
444     {
445         var commandLineAPI = null;
446         if (injectCommandLineAPI) {
447             if (this.CommandLineAPI)
448                 commandLineAPI = new this.CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
449             else
450                 commandLineAPI = new BasicCommandLineAPI;
451         }
452
453         if (isEvalOnCallFrame) {
454             // We can only use this approach if the evaluate function is the true 'eval'. That allows us to use it with
455             // the 'eval' identifier when calling it. Using 'eval' grants access to the local scope of the closure we
456             // create that provides the command line APIs.
457
458             var parameters = [InjectedScriptHost.evaluate, expression];
459             var expressionFunctionBody = "" +
460                 "var global = Function('return this')() || (1, eval)('this');" +
461                 "var __originalEval = global.eval; global.eval = __eval;" +
462                 "try { return eval(__currentExpression); }" +
463                 "finally { global.eval = __originalEval; }";
464
465             if (commandLineAPI) {
466                 // To avoid using a 'with' statement (which fails in strict mode and requires injecting the API object)
467                 // we instead create a closure where we evaluate the expression. The command line APIs are passed as
468                 // parameters to the closure so they are in scope but not injected. This allows the code evaluated in
469                 // the console to stay in strict mode (if is was already set), or to get strict mode by prefixing
470                 // expressions with 'use strict';.
471
472                 var parameterNames = Object.getOwnPropertyNames(commandLineAPI);
473                 for (var i = 0; i < parameterNames.length; ++i)
474                     parameters.push(commandLineAPI[parameterNames[i]]);
475
476                 var expressionFunctionString = "(function(__eval, __currentExpression, " + parameterNames.join(", ") + ") { " + expressionFunctionBody + " })";
477             } else {
478                 // Use a closure in this case too to keep the same behavior of 'var' being captured by the closure instead
479                 // of leaking out into the calling scope.
480                 var expressionFunctionString = "(function(__eval, __currentExpression) { " + expressionFunctionBody + " })";
481             }
482
483             // Bind 'this' to the function expression using another closure instead of Function.prototype.bind. This ensures things will work if the page replaces bind.
484             var boundExpressionFunctionString = "(function(__function, __thisObject) { return function() { return __function.apply(__thisObject, arguments) }; })(" + expressionFunctionString + ", this)";
485             var expressionFunction = evalFunction.call(object, boundExpressionFunctionString);
486             var result = expressionFunction.apply(null, parameters);
487
488             if (objectGroup === "console" && saveResult)
489                 this._saveResult(result);
490
491             return result;
492         }
493
494         // When not evaluating on a call frame we use a 'with' statement to allow var and function statements to leak
495         // into the global scope. This allow them to stick around between evaluations.
496
497         try {
498             if (commandLineAPI) {
499                 if (inspectedGlobalObject.console)
500                     inspectedGlobalObject.console.__commandLineAPI = commandLineAPI;
501                 else
502                     inspectedGlobalObject.__commandLineAPI = commandLineAPI;
503                 expression = "with ((this && (this.console ? this.console.__commandLineAPI : this.__commandLineAPI)) || {}) { " + expression + "\n}";
504             }
505
506             var result = evalFunction.call(inspectedGlobalObject, expression);
507
508             if (objectGroup === "console" && saveResult)
509                 this._saveResult(result);
510
511             return result;
512         } finally {
513             if (commandLineAPI) {
514                 if (inspectedGlobalObject.console)
515                     delete inspectedGlobalObject.console.__commandLineAPI;
516                 else
517                     delete inspectedGlobalObject.__commandLineAPI;
518             }
519         }
520     },
521
522     wrapCallFrames: function(callFrame)
523     {
524         if (!callFrame)
525             return false;
526
527         var result = [];
528         var depth = 0;
529         do {
530             result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
531             callFrame = callFrame.caller;
532         } while (callFrame);
533         return result;
534     },
535
536     evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
537     {
538         var callFrame = this._callFrameForId(topCallFrame, callFrameId);
539         if (!callFrame)
540             return "Could not find call frame with given id";
541         return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
542     },
543
544     _callFrameForId: function(topCallFrame, callFrameId)
545     {
546         var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")");
547         var ordinal = parsedCallFrameId["ordinal"];
548         var callFrame = topCallFrame;
549         while (--ordinal >= 0 && callFrame)
550             callFrame = callFrame.caller;
551         return callFrame;
552     },
553
554     _objectForId: function(objectId)
555     {
556         return this._idToWrappedObject[objectId.id];
557     },
558
559     findObjectById: function(objectId)
560     {
561         var parsedObjectId = this._parseObjectId(objectId);
562         return this._objectForId(parsedObjectId);
563     },
564
565     module: function(name)
566     {
567         return this._modules[name];
568     },
569
570     injectModule: function(name, source, host)
571     {
572         delete this._modules[name];
573
574         var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")");
575         if (typeof moduleFunction !== "function") {
576             if (inspectedGlobalObject.console)
577                 inspectedGlobalObject.console.error("Web Inspector error: A function was expected for module %s evaluation", name);
578             return null;
579         }
580
581         var module = moduleFunction.call(inspectedGlobalObject, InjectedScriptHost, inspectedGlobalObject, injectedScriptId, this, host);
582         this._modules[name] = module;
583         return module;
584     },
585
586     _internalPropertyDescriptors: function(object, completeDescriptor)
587     {
588         var internalProperties = InjectedScriptHost.getInternalProperties(object);
589         if (!internalProperties)
590             return null;
591
592         var descriptors = [];
593         for (var i = 0; i < internalProperties.length; i++) {
594             var property = internalProperties[i];
595             var descriptor = {name: property.name, value: property.value};
596             if (completeDescriptor) {
597                 descriptor.writable = false;
598                 descriptor.configurable = false;
599                 descriptor.enumerable = false;
600                 descriptor.isOwn = true;
601             }
602             descriptors.push(descriptor);
603         }
604         return descriptors;
605     },
606
607     _propertyDescriptors: function(object, collectionMode)
608     {
609         var descriptors = [];
610         var nameProcessed = {};
611         nameProcessed["__proto__"] = null;
612
613         function createFakeValueDescriptor(name, descriptor, isOwnProperty, possibleNativeBindingGetter)
614         {
615             try {
616                 var descriptor = {name, value: object[name], writable: descriptor.writable || false, configurable: descriptor.configurable || false, enumerable: descriptor.enumerable || false};
617                 if (possibleNativeBindingGetter)
618                     descriptor.nativeGetter = true;
619                 return descriptor;
620             } catch (e) {
621                 var errorDescriptor = {name, value: e, wasThrown: true};
622                 if (isOwnProperty)
623                     errorDescriptor.isOwn = true;
624                 return errorDescriptor;
625             }
626         }
627
628         function processDescriptor(descriptor, isOwnProperty, possibleNativeBindingGetter)
629         {
630             // All properties.
631             if (collectionMode & InjectedScript.CollectionMode.AllProperties) {
632                 descriptors.push(descriptor);
633                 return;
634             }
635
636             // Own properties.
637             if (collectionMode & InjectedScript.CollectionMode.OwnProperties && isOwnProperty) {
638                 descriptors.push(descriptor);
639                 return;
640             }
641
642             // Native Getter properties.
643             if (collectionMode & InjectedScript.CollectionMode.NativeGetterProperties) {
644                 // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete
645                 // if (descriptor.hasOwnProperty("get") && descriptor.get && isNativeFunction(descriptor.get)) { ... }
646
647                 if (possibleNativeBindingGetter) {
648                     // Possible getter property in the prototype chain.
649                     descriptors.push(descriptor);
650                     return;
651                 }
652             }
653         }
654
655         function processPropertyNames(o, names, isOwnProperty)
656         {
657             for (var i = 0; i < names.length; ++i) {
658                 var name = names[i];
659                 if (nameProcessed[name] || name === "__proto__")
660                     continue;
661
662                 nameProcessed[name] = true;
663
664                 var descriptor = Object.getOwnPropertyDescriptor(o, name);
665                 if (!descriptor) {
666                     // FIXME: Bad descriptor. Can we get here?
667                     // Fall back to very restrictive settings.
668                     var fakeDescriptor = createFakeValueDescriptor(name, {writable: false, configurable: false, enumerable: false}, isOwnProperty);
669                     processDescriptor(fakeDescriptor, isOwnProperty);
670                     continue;
671                 }
672
673                 if (descriptor.hasOwnProperty("get") && descriptor.hasOwnProperty("set") && !descriptor.get && !descriptor.set) {
674                     // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete
675                     // Developers may create such a descriptors, so we should be resilient:
676                     // var x = {}; Object.defineProperty(x, "p", {get:undefined}); Object.getOwnPropertyDescriptor(x, "p")
677                     var fakeDescriptor = createFakeValueDescriptor(name, descriptor, isOwnProperty, true);
678                     processDescriptor(fakeDescriptor, isOwnProperty, true);
679                     continue;
680                 }
681
682                 descriptor.name = name;
683                 if (isOwnProperty)
684                     descriptor.isOwn = true;
685                 processDescriptor(descriptor, isOwnProperty);
686             }
687         }
688
689         // Iterate prototype chain.
690         for (var o = object; this._isDefined(o); o = o.__proto__) {
691             var isOwnProperty = o === object;
692             processPropertyNames(o, Object.getOwnPropertyNames(o), isOwnProperty);
693             if (collectionMode === InjectedScript.CollectionMode.OwnProperties)
694                 break;
695         }
696
697         // Always include __proto__ at the end.
698         try {
699             if (object.__proto__)
700                 descriptors.push({name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true});
701         } catch (e) {}
702
703         return descriptors;
704     },
705
706     _isDefined: function(object)
707     {
708         return !!object || this._isHTMLAllCollection(object);
709     },
710
711     _isHTMLAllCollection: function(object)
712     {
713         // document.all is reported as undefined, but we still want to process it.
714         return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object);
715     },
716
717     _subtype: function(obj)
718     {
719         if (obj === null)
720             return "null";
721
722         if (this.isPrimitiveValue(obj) || isSymbol(obj))
723             return null;
724
725         if (this._isHTMLAllCollection(obj))
726             return "array";
727
728         var preciseType = InjectedScriptHost.subtype(obj);
729         if (preciseType)
730             return preciseType;
731
732         // FireBug's array detection.
733         try {
734             if (typeof obj.splice === "function" && isFinite(obj.length))
735                 return "array";
736             if (Object.prototype.toString.call(obj) === "[object Arguments]" && isFinite(obj.length)) // arguments.
737                 return "array";
738         } catch (e) {
739         }
740
741         return null;
742     },
743
744     _describe: function(obj)
745     {
746         if (this.isPrimitiveValue(obj))
747             return null;
748
749         if (isSymbol(obj))
750             return toString(obj);
751
752         var subtype = this._subtype(obj);
753
754         if (subtype === "regexp")
755             return toString(obj);
756
757         if (subtype === "date")
758             return toString(obj);
759
760         if (subtype === "error")
761             return toString(obj);
762
763         if (subtype === "node") {
764             var description = obj.nodeName.toLowerCase();
765             switch (obj.nodeType) {
766             case 1 /* Node.ELEMENT_NODE */:
767                 description += obj.id ? "#" + obj.id : "";
768                 var className = obj.className;
769                 description += (className && typeof className === "string") ? "." + className.trim().replace(/\s+/g, ".") : "";
770                 break;
771             case 10 /*Node.DOCUMENT_TYPE_NODE */:
772                 description = "<!DOCTYPE " + description + ">";
773                 break;
774             }
775             return description;
776         }
777
778         var className = InjectedScriptHost.internalConstructorName(obj);
779         if (subtype === "array")
780             return className;
781
782         if (subtype === "class")
783             return obj.name;
784
785         // NodeList in JSC is a function, check for array prior to this.
786         if (typeof obj === "function")
787             return toString(obj);
788
789         // If Object, try for a better name from the constructor.
790         if (className === "Object") {
791             var constructorName = obj.constructor && obj.constructor.name;
792             if (constructorName)
793                 return constructorName;
794         }
795
796         return className;
797     },
798
799     _getSetEntries: function(object, skip, numberToFetch)
800     {
801         var entries = [];
802
803         for (var value of object) {
804             if (skip > 0) {
805                 skip--;
806                 continue;
807             }
808
809             entries.push({value});
810
811             if (numberToFetch && entries.length === numberToFetch)
812                 break;
813         }
814
815         return entries;
816     },
817
818     _getMapEntries: function(object, skip, numberToFetch)
819     {
820         var entries = [];
821
822         for (var [key, value] of object) {
823             if (skip > 0) {
824                 skip--;
825                 continue;
826             }
827
828             entries.push({key, value});
829
830             if (numberToFetch && entries.length === numberToFetch)
831                 break;
832         }
833
834         return entries;
835     },
836
837     _getWeakMapEntries: function(object, numberToFetch)
838     {
839         return InjectedScriptHost.weakMapEntries(object, numberToFetch);
840     },
841
842     _getIteratorEntries: function(object, numberToFetch)
843     {
844         return InjectedScriptHost.iteratorEntries(object, numberToFetch);
845     },
846
847     _entries: function(object, subtype, startIndex, numberToFetch)
848     {
849         if (subtype === "set")
850             return this._getSetEntries(object, startIndex, numberToFetch);
851         if (subtype === "map")
852             return this._getMapEntries(object, startIndex, numberToFetch);
853         if (subtype === "weakmap")
854             return this._getWeakMapEntries(object, numberToFetch);
855         if (subtype === "iterator")
856             return this._getIteratorEntries(object, numberToFetch);
857
858         throw "unexpected type";
859     },
860
861     _saveResult: function(result)
862     {
863         this._lastResult = result;
864
865         if (result === undefined || result === null)
866             return;
867
868         var existingIndex = this._savedResults.indexOf(result);
869         if (existingIndex !== -1) {
870             this._savedResultIndex = existingIndex;
871             return;
872         }
873
874         this._savedResultIndex = this._nextSavedResultIndex;
875         this._savedResults[this._nextSavedResultIndex++] = result;
876
877         // $n is limited from $1-$99. $0 is special.
878         if (this._nextSavedResultIndex >= 100)
879             this._nextSavedResultIndex = 1;
880     },
881
882     _savedResult: function(index)
883     {
884         return this._savedResults[index];
885     }
886 }
887
888 var injectedScript = new InjectedScript;
889
890
891 InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames)
892 {
893     this.type = typeof object;
894
895     if (this.type === "undefined" && injectedScript._isHTMLAllCollection(object))
896         this.type = "object";
897
898     if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
899         // We don't send undefined values over JSON.
900         if (this.type !== "undefined")
901             this.value = object;
902
903         // Null object is object with 'null' subtype.
904         if (object === null)
905             this.subtype = "null";
906
907         // Provide user-friendly number values.
908         if (this.type === "number")
909             this.description = toStringDescription(object);
910         return;
911     }
912
913     this.objectId = injectedScript._bind(object, objectGroupName);
914
915     var subtype = injectedScript._subtype(object);
916     if (subtype)
917         this.subtype = subtype;
918
919     this.className = InjectedScriptHost.internalConstructorName(object);
920     this.description = injectedScript._describe(object);
921
922     if (subtype === "array")
923         this.size = typeof object.length === "number" ? object.length : 0;
924     else if (subtype === "set" || subtype === "map")
925         this.size = object.size;
926     else if (subtype === "weakmap")
927         this.size = InjectedScriptHost.weakMapSize(object);
928     else if (subtype === "class")
929         this.classPrototype = injectedScript._wrapObject(object.prototype, objectGroupName);
930
931     if (generatePreview && this.type === "object")
932         this.preview = this._generatePreview(object, undefined, columnNames);
933 }
934
935 InjectedScript.RemoteObject.prototype = {
936     _emptyPreview: function()
937     {
938         var preview = {
939             type: this.type,
940             description: this.description || toString(this.value),
941             lossless: true,
942         };
943
944         if (this.subtype) {
945             preview.subtype = this.subtype;
946             if (this.subtype !== "null") {
947                 preview.overflow = false;
948                 preview.properties = [];
949             }
950         }
951
952         if ("size" in this)
953             preview.size = this.size;
954
955         return preview;
956     },
957
958     _createObjectPreviewForValue: function(value)
959     {
960         var remoteObject = new InjectedScript.RemoteObject(value, undefined, false, true, undefined);
961         if (remoteObject.objectId)
962             injectedScript.releaseObject(remoteObject.objectId);
963         if (remoteObject.classPrototype && remoteObject.classPrototype.objectId)
964             injectedScript.releaseObject(remoteObject.classPrototype.objectId);
965
966         return remoteObject.preview || remoteObject._emptyPreview();
967     },
968
969     _generatePreview: function(object, firstLevelKeys, secondLevelKeys)
970     {
971         var preview = this._emptyPreview();
972
973         // Primitives just have a value.
974         if (this.type !== "object")
975             return;
976
977         var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys;
978         var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
979
980         var propertiesThreshold = {
981             properties: isTableRowsRequest ? 1000 : Math.max(5, firstLevelKeysCount),
982             indexes: isTableRowsRequest ? 1000 : Math.max(100, firstLevelKeysCount)
983         };
984
985         try {
986             // Maps, Sets, and Iterators have entries.
987             if (this.subtype === "map" || this.subtype === "set" || this.subtype === "weakmap" || this.subtype === "iterator")
988                 this._appendEntryPreviews(object, preview);
989
990             preview.properties = [];
991
992             // Internal Properties.
993             var internalPropertyDescriptors = injectedScript._internalPropertyDescriptors(object, true);
994             if (internalPropertyDescriptors) {
995                 this._appendPropertyPreviews(preview, internalPropertyDescriptors, true, propertiesThreshold, firstLevelKeys, secondLevelKeys);
996                 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
997                     return preview;
998             }
999
1000             if (preview.entries)
1001                 return preview;
1002
1003             // Properties.
1004             var descriptors = injectedScript._propertyDescriptors(object, InjectedScript.CollectionMode.AllProperties);
1005             this._appendPropertyPreviews(preview, descriptors, false, propertiesThreshold, firstLevelKeys, secondLevelKeys);
1006             if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
1007                 return preview;
1008         } catch (e) {
1009             preview.lossless = false;
1010         }
1011
1012         return preview;
1013     },
1014
1015     _appendPropertyPreviews: function(preview, descriptors, internal, propertiesThreshold, firstLevelKeys, secondLevelKeys)
1016     {
1017         for (var descriptor of descriptors) {
1018             // Seen enough.
1019             if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
1020                 break;
1021
1022             // Error in descriptor.
1023             if (descriptor.wasThrown) {
1024                 preview.lossless = false;
1025                 continue;
1026             }
1027
1028             // Do not show "__proto__" in preview.
1029             var name = descriptor.name;
1030             if (name === "__proto__")
1031                 continue;
1032
1033             // Do not show "length" on array like objects in preview.
1034             if (this.subtype === "array" && name === "length")
1035                 continue;
1036
1037             // Do not show non-enumerable non-own properties. Special case to allow array indexes that may be on the prototype.
1038             if (!descriptor.enumerable && !descriptor.isOwn && !(this.subtype === "array" && isUInt32(name)))
1039                 continue;
1040
1041             // If we have a filter, only show properties in the filter.
1042             if (firstLevelKeys && firstLevelKeys.indexOf(name) === -1)
1043                 continue;
1044
1045             // Getter/setter.
1046             if (!("value" in descriptor)) {
1047                 preview.lossless = false;
1048                 this._appendPropertyPreview(preview, internal, {name, type: "accessor"}, propertiesThreshold);
1049                 continue;
1050             }
1051
1052             // Null value.
1053             var value = descriptor.value;
1054             if (value === null) {
1055                 this._appendPropertyPreview(preview, internal, {name, type: "object", subtype: "null", value: "null"}, propertiesThreshold);
1056                 continue;
1057             }
1058
1059             // Ignore non-enumerable functions.
1060             var type = typeof value;
1061             if (!descriptor.enumerable && type === "function")
1062                 continue;
1063
1064             // Fix type of document.all.
1065             if (type === "undefined" && injectedScript._isHTMLAllCollection(value))
1066                 type = "object";
1067
1068             // Primitive.
1069             const maxLength = 100;
1070             if (InjectedScript.primitiveTypes[type]) {
1071                 if (type === "string" && value.length > maxLength) {
1072                     value = this._abbreviateString(value, maxLength, true);
1073                     preview.lossless = false;
1074                 }
1075                 this._appendPropertyPreview(preview, internal, {name, type, value: toStringDescription(value)}, propertiesThreshold);
1076                 continue;
1077             }
1078
1079             // Symbol.
1080             if (isSymbol(value)) {
1081                 var symbolString = toString(value);
1082                 if (symbolString.length > maxLength) {
1083                     symbolString = this._abbreviateString(symbolString, maxLength, true);
1084                     preview.lossless = false;
1085                 }
1086                 this._appendPropertyPreview(preview, internal, {name, type, value: symbolString}, propertiesThreshold);
1087                 return;
1088             }
1089
1090             // Object.
1091             var property = {name, type};
1092             var subtype = injectedScript._subtype(value);
1093             if (subtype)
1094                 property.subtype = subtype;
1095
1096             // Second level.
1097             if (secondLevelKeys === null || secondLevelKeys) {
1098                 var subPreview = this._generatePreview(value, secondLevelKeys || undefined, undefined);
1099                 property.valuePreview = subPreview;
1100                 if (!subPreview.lossless)
1101                     preview.lossless = false;
1102                 if (subPreview.overflow)
1103                     preview.overflow = true;
1104             } else if (this._isPreviewableObject(value)) {
1105                 var subPreview = this._createObjectPreviewForValue(value);
1106                 property.valuePreview = subPreview;
1107                 if (!subPreview.lossless)
1108                     preview.lossless = false;
1109                 if (subPreview.overflow)
1110                     preview.overflow = true;
1111             } else {
1112                 var description = "";
1113                 if (type !== "function" || subtype === "class")
1114                     description = this._abbreviateString(injectedScript._describe(value), maxLength, subtype === "regexp");
1115                 property.value = description;
1116                 preview.lossless = false;
1117             }
1118
1119             this._appendPropertyPreview(preview, internal, property, propertiesThreshold);
1120         }
1121     },
1122
1123     _appendPropertyPreview: function(preview, internal, property, propertiesThreshold)
1124     {
1125         if (toString(property.name >>> 0) === property.name)
1126             propertiesThreshold.indexes--;
1127         else
1128             propertiesThreshold.properties--;
1129
1130         if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) {
1131             preview.overflow = true;
1132             preview.lossless = false;
1133             return;
1134         }
1135
1136         if (internal)
1137             property.internal = true;
1138
1139         preview.properties.push(property);
1140     },
1141
1142     _appendEntryPreviews: function(object, preview)
1143     {
1144         // Fetch 6, but only return 5, so we can tell if we overflowed.
1145         var entries = injectedScript._entries(object, this.subtype, 0, 6);
1146         if (!entries)
1147             return;
1148
1149         if (entries.length > 5) {
1150             entries.pop();
1151             preview.overflow = true;
1152             preview.lossless = false;
1153         }
1154
1155         preview.entries = entries.map(function(entry) {
1156             entry.value = this._createObjectPreviewForValue(entry.value);
1157             if ("key" in entry)
1158                 entry.key = this._createObjectPreviewForValue(entry.key);
1159             return entry;
1160         }, this);
1161     },
1162
1163     _isPreviewableObject: function(object)
1164     {
1165         return this._isPreviewableObjectInternal(object, new Set, 1);
1166     },
1167
1168     _isPreviewableObjectInternal: function(object, knownObjects, depth)
1169     {
1170         // Deep object.
1171         if (depth > 3)
1172             return false;
1173
1174         // Primitive.
1175         if (injectedScript.isPrimitiveValue(object) || isSymbol(object))
1176             return true;
1177
1178         // Cyclic objects.
1179         if (knownObjects.has(object))
1180             return false;
1181
1182         ++depth;
1183         knownObjects.add(object);
1184
1185         // Arrays are simple if they have 5 or less simple objects.
1186         var subtype = injectedScript._subtype(object);
1187         if (subtype === "array") {
1188             var length = object.length;
1189             if (length > 5)
1190                 return false;
1191             for (var i = 0; i < length; ++i) {
1192                 if (!this._isPreviewableObjectInternal(object[i], knownObjects, depth))
1193                     return false;
1194             }
1195             return true;
1196         }
1197
1198         // Not a basic object.
1199         if (object.__proto__ && object.__proto__.__proto__)
1200             return false;
1201
1202         // Objects are simple if they have 3 or less simple properties.
1203         var ownPropertyNames = Object.getOwnPropertyNames(object);
1204         if (ownPropertyNames.length > 3)
1205             return false;
1206         for (var propertyName of ownPropertyNames) {
1207             if (!this._isPreviewableObjectInternal(object[propertyName], knownObjects, depth))
1208                 return false;
1209         }
1210
1211         return true;
1212     },
1213
1214     _abbreviateString: function(string, maxLength, middle)
1215     {
1216         if (string.length <= maxLength)
1217             return string;
1218
1219         if (middle) {
1220             var leftHalf = maxLength >> 1;
1221             var rightHalf = maxLength - leftHalf - 1;
1222             return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf);
1223         }
1224
1225         return string.substr(0, maxLength) + "\u2026";
1226     }
1227 }
1228
1229 InjectedScript.CallFrameProxy = function(ordinal, callFrame)
1230 {
1231     this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}";
1232     this.functionName = (callFrame.type === "function" ? callFrame.functionName : "");
1233     this.location = {scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column};
1234     this.scopeChain = this._wrapScopeChain(callFrame);
1235     this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace");
1236 }
1237
1238 InjectedScript.CallFrameProxy.prototype = {
1239     _wrapScopeChain: function(callFrame)
1240     {
1241         var scopeChain = callFrame.scopeChain;
1242         var scopeChainProxy = [];
1243         for (var i = 0; i < scopeChain.length; i++)
1244             scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace");
1245         return scopeChainProxy;
1246     }
1247 }
1248
1249 InjectedScript.CallFrameProxy._scopeTypeNames = {
1250     0: "global", // GLOBAL_SCOPE
1251     1: "local", // LOCAL_SCOPE
1252     2: "with", // WITH_SCOPE
1253     3: "closure", // CLOSURE_SCOPE
1254     4: "catch", // CATCH_SCOPE
1255     5: "functionName", // FUNCTION_NAME_SCOPE
1256 }
1257
1258 InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId)
1259 {
1260     return {
1261         object: injectedScript._wrapObject(scopeObject, groupId),
1262         type: InjectedScript.CallFrameProxy._scopeTypeNames[scopeTypeCode]
1263     };
1264 }
1265
1266
1267 function slice(array, index)
1268 {
1269     var result = [];
1270     for (var i = index || 0; i < array.length; ++i)
1271         result.push(array[i]);
1272     return result;
1273 }
1274
1275 function bind(func, thisObject, var_args)
1276 {
1277     var args = slice(arguments, 2);
1278     return function(var_args) {
1279         return func.apply(thisObject, args.concat(slice(arguments)));
1280     }
1281 }
1282
1283 function BasicCommandLineAPI()
1284 {
1285     this.$_ = injectedScript._lastResult;
1286     this.$exception = injectedScript._exceptionValue;
1287
1288     // $1-$99
1289     for (var i = 1; i <= injectedScript._savedResults.length; ++i) {
1290         var member = "$" + i;
1291         if (member in inspectedGlobalObject)
1292             continue;
1293         this.__defineGetter__("$" + i, bind(injectedScript._savedResult, injectedScript, i));
1294     }
1295 }
1296
1297 return injectedScript;
1298 })