Web Inspector: ES6: Show Symbol properties on Objects
[WebKit-https.git] / Source / JavaScriptCore / inspector / InjectedScriptSource.js
1 /*
2  * Copyright (C) 2007, 2014-2015 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             if ("symbol" in descriptor)
243                 descriptor.symbol = this._wrapObject(descriptor.symbol, objectGroupName);
244         }
245
246         return descriptors;
247     },
248
249     getProperties: function(objectId, ownProperties, generatePreview)
250     {
251         var collectionMode = ownProperties ? InjectedScript.CollectionMode.OwnProperties : InjectedScript.CollectionMode.AllProperties;
252         return this._getProperties(objectId, collectionMode, generatePreview);
253     },
254
255     getDisplayableProperties: function(objectId, generatePreview)
256     {
257         var collectionMode = InjectedScript.CollectionMode.OwnProperties | InjectedScript.CollectionMode.NativeGetterProperties;
258         return this._getProperties(objectId, collectionMode, generatePreview);
259     },
260
261     getInternalProperties: function(objectId, generatePreview)
262     {
263         var parsedObjectId = this._parseObjectId(objectId);
264         var object = this._objectForId(parsedObjectId);
265         var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
266
267         if (!this._isDefined(object))
268             return false;
269
270         if (isSymbol(object))
271             return false;
272
273         var descriptors = this._internalPropertyDescriptors(object);
274         if (!descriptors)
275             return [];
276
277         // Go over properties, wrap object values.
278         for (var i = 0; i < descriptors.length; ++i) {
279             var descriptor = descriptors[i];
280             if ("value" in descriptor)
281                 descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
282         }
283
284         return descriptors;
285     },
286
287     getCollectionEntries: function(objectId, objectGroupName, startIndex, numberToFetch)
288     {
289         var parsedObjectId = this._parseObjectId(objectId);
290         var object = this._objectForId(parsedObjectId);
291         var objectGroupName = objectGroupName || this._idToObjectGroupName[parsedObjectId.id];
292
293         if (!this._isDefined(object))
294             return;
295
296         if (typeof object !== "object")
297             return;
298
299         var entries = this._entries(object, InjectedScriptHost.subtype(object), startIndex, numberToFetch);
300         return entries.map(function(entry) {
301             entry.value = injectedScript._wrapObject(entry.value, objectGroupName, false, true);
302             if ("key" in entry)
303                 entry.key = injectedScript._wrapObject(entry.key, objectGroupName, false, true);
304             return entry;
305         });
306     },
307
308     saveResult: function(callArgumentJSON)
309     {
310         this._savedResultIndex = 0;
311
312         try {
313             var callArgument = InjectedScriptHost.evaluate("(" + callArgumentJSON + ")");
314             var value = this._resolveCallArgument(callArgument);
315             this._saveResult(value);
316         } catch (e) {}
317
318         return this._savedResultIndex;
319     },
320
321     getFunctionDetails: function(functionId)
322     {
323         var parsedFunctionId = this._parseObjectId(functionId);
324         var func = this._objectForId(parsedFunctionId);
325         if (typeof func !== "function")
326             return "Cannot resolve function by id.";
327         var details = InjectedScriptHost.functionDetails(func);
328         if (!details)
329             return "Cannot resolve function details.";
330         if ("rawScopes" in details) {
331             var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id];
332             var rawScopes = details.rawScopes;
333             var scopes = [];
334             delete details.rawScopes;
335             for (var i = 0; i < rawScopes.length; i++)
336                 scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName));
337             details.scopeChain = scopes;
338         }
339         return details;
340     },
341
342     releaseObject: function(objectId)
343     {
344         var parsedObjectId = this._parseObjectId(objectId);
345         this._releaseObject(parsedObjectId.id);
346     },
347
348     _releaseObject: function(id)
349     {
350         delete this._idToWrappedObject[id];
351         delete this._idToObjectGroupName[id];
352     },
353
354     evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
355     {
356         return this._evaluateAndWrap(InjectedScriptHost.evaluate, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
357     },
358
359     callFunctionOn: function(objectId, expression, args, returnByValue, generatePreview)
360     {
361         var parsedObjectId = this._parseObjectId(objectId);
362         var object = this._objectForId(parsedObjectId);
363         if (!this._isDefined(object))
364             return "Could not find object with given id";
365
366         if (args) {
367             var resolvedArgs = [];
368             var callArgs = InjectedScriptHost.evaluate(args);
369             for (var i = 0; i < callArgs.length; ++i) {
370                 try {
371                     resolvedArgs[i] = this._resolveCallArgument(callArgs[i]);
372                 } catch (e) {
373                     return String(e);
374                 }
375             }
376         }
377
378         try {
379             var objectGroup = this._idToObjectGroupName[parsedObjectId.id];
380             var func = InjectedScriptHost.evaluate("(" + expression + ")");
381             if (typeof func !== "function")
382                 return "Given expression does not evaluate to a function";
383
384             return {
385                 wasThrown: false,
386                 result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue, generatePreview)
387             };
388         } catch (e) {
389             return this._createThrownValue(e, objectGroup);
390         }
391     },
392
393     _resolveCallArgument: function(callArgumentJSON)
394     {
395         if ("value" in callArgumentJSON)
396             return callArgumentJSON.value;
397
398         var objectId = callArgumentJSON.objectId;
399         if (objectId) {
400             var parsedArgId = this._parseObjectId(objectId);
401             if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId)
402                 throw "Arguments should belong to the same JavaScript world as the target object.";
403
404             var resolvedArg = this._objectForId(parsedArgId);
405             if (!this._isDefined(resolvedArg))
406                 throw "Could not find object with given id";
407
408             return resolvedArg;
409         }
410
411         return undefined;
412     },
413
414     _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
415     {
416         try {
417             this._savedResultIndex = 0;
418
419             var returnObject = {
420                 wasThrown: false,
421                 result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult), objectGroup, returnByValue, generatePreview)
422             };
423
424             if (saveResult && this._savedResultIndex)
425                 returnObject.savedResultIndex = this._savedResultIndex;
426
427             return returnObject;
428         } catch (e) {
429             return this._createThrownValue(e, objectGroup);
430         }
431     },
432
433     _createThrownValue: function(value, objectGroup)
434     {
435         var remoteObject = this._wrapObject(value, objectGroup);
436         try {
437             remoteObject.description = toStringDescription(value);
438         } catch (e) {}
439         return {
440             wasThrown: true,
441             result: remoteObject
442         };
443     },
444
445     _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult)
446     {
447         var commandLineAPI = null;
448         if (injectCommandLineAPI) {
449             if (this.CommandLineAPI)
450                 commandLineAPI = new this.CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
451             else
452                 commandLineAPI = new BasicCommandLineAPI;
453         }
454
455         if (isEvalOnCallFrame) {
456             // We can only use this approach if the evaluate function is the true 'eval'. That allows us to use it with
457             // the 'eval' identifier when calling it. Using 'eval' grants access to the local scope of the closure we
458             // create that provides the command line APIs.
459
460             var parameters = [InjectedScriptHost.evaluate, expression];
461             var expressionFunctionBody = "" +
462                 "var global = Function('return this')() || (1, eval)('this');" +
463                 "var __originalEval = global.eval; global.eval = __eval;" +
464                 "try { return eval(__currentExpression); }" +
465                 "finally { global.eval = __originalEval; }";
466
467             if (commandLineAPI) {
468                 // To avoid using a 'with' statement (which fails in strict mode and requires injecting the API object)
469                 // we instead create a closure where we evaluate the expression. The command line APIs are passed as
470                 // parameters to the closure so they are in scope but not injected. This allows the code evaluated in
471                 // the console to stay in strict mode (if is was already set), or to get strict mode by prefixing
472                 // expressions with 'use strict';.
473
474                 var parameterNames = Object.getOwnPropertyNames(commandLineAPI);
475                 for (var i = 0; i < parameterNames.length; ++i)
476                     parameters.push(commandLineAPI[parameterNames[i]]);
477
478                 var expressionFunctionString = "(function(__eval, __currentExpression, " + parameterNames.join(", ") + ") { " + expressionFunctionBody + " })";
479             } else {
480                 // Use a closure in this case too to keep the same behavior of 'var' being captured by the closure instead
481                 // of leaking out into the calling scope.
482                 var expressionFunctionString = "(function(__eval, __currentExpression) { " + expressionFunctionBody + " })";
483             }
484
485             // Bind 'this' to the function expression using another closure instead of Function.prototype.bind. This ensures things will work if the page replaces bind.
486             var boundExpressionFunctionString = "(function(__function, __thisObject) { return function() { return __function.apply(__thisObject, arguments) }; })(" + expressionFunctionString + ", this)";
487             var expressionFunction = evalFunction.call(object, boundExpressionFunctionString);
488             var result = expressionFunction.apply(null, parameters);
489
490             if (objectGroup === "console" && saveResult)
491                 this._saveResult(result);
492
493             return result;
494         }
495
496         // When not evaluating on a call frame we use a 'with' statement to allow var and function statements to leak
497         // into the global scope. This allow them to stick around between evaluations.
498
499         try {
500             if (commandLineAPI) {
501                 if (inspectedGlobalObject.console)
502                     inspectedGlobalObject.console.__commandLineAPI = commandLineAPI;
503                 else
504                     inspectedGlobalObject.__commandLineAPI = commandLineAPI;
505                 expression = "with ((this && (this.console ? this.console.__commandLineAPI : this.__commandLineAPI)) || {}) { " + expression + "\n}";
506             }
507
508             var result = evalFunction.call(inspectedGlobalObject, expression);
509
510             if (objectGroup === "console" && saveResult)
511                 this._saveResult(result);
512
513             return result;
514         } finally {
515             if (commandLineAPI) {
516                 if (inspectedGlobalObject.console)
517                     delete inspectedGlobalObject.console.__commandLineAPI;
518                 else
519                     delete inspectedGlobalObject.__commandLineAPI;
520             }
521         }
522     },
523
524     wrapCallFrames: function(callFrame)
525     {
526         if (!callFrame)
527             return false;
528
529         var result = [];
530         var depth = 0;
531         do {
532             result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
533             callFrame = callFrame.caller;
534         } while (callFrame);
535         return result;
536     },
537
538     evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
539     {
540         var callFrame = this._callFrameForId(topCallFrame, callFrameId);
541         if (!callFrame)
542             return "Could not find call frame with given id";
543         return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
544     },
545
546     _callFrameForId: function(topCallFrame, callFrameId)
547     {
548         var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")");
549         var ordinal = parsedCallFrameId["ordinal"];
550         var callFrame = topCallFrame;
551         while (--ordinal >= 0 && callFrame)
552             callFrame = callFrame.caller;
553         return callFrame;
554     },
555
556     _objectForId: function(objectId)
557     {
558         return this._idToWrappedObject[objectId.id];
559     },
560
561     findObjectById: function(objectId)
562     {
563         var parsedObjectId = this._parseObjectId(objectId);
564         return this._objectForId(parsedObjectId);
565     },
566
567     module: function(name)
568     {
569         return this._modules[name];
570     },
571
572     injectModule: function(name, source, host)
573     {
574         delete this._modules[name];
575
576         var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")");
577         if (typeof moduleFunction !== "function") {
578             if (inspectedGlobalObject.console)
579                 inspectedGlobalObject.console.error("Web Inspector error: A function was expected for module %s evaluation", name);
580             return null;
581         }
582
583         var module = moduleFunction.call(inspectedGlobalObject, InjectedScriptHost, inspectedGlobalObject, injectedScriptId, this, host);
584         this._modules[name] = module;
585         return module;
586     },
587
588     _internalPropertyDescriptors: function(object, completeDescriptor)
589     {
590         var internalProperties = InjectedScriptHost.getInternalProperties(object);
591         if (!internalProperties)
592             return null;
593
594         var descriptors = [];
595         for (var i = 0; i < internalProperties.length; i++) {
596             var property = internalProperties[i];
597             var descriptor = {name: property.name, value: property.value};
598             if (completeDescriptor) {
599                 descriptor.writable = false;
600                 descriptor.configurable = false;
601                 descriptor.enumerable = false;
602                 descriptor.isOwn = true;
603             }
604             descriptors.push(descriptor);
605         }
606         return descriptors;
607     },
608
609     _propertyDescriptors: function(object, collectionMode)
610     {
611         var descriptors = [];
612         var nameProcessed = new Set;
613
614         function createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty, possibleNativeBindingGetter)
615         {
616             try {
617                 var descriptor = {name, value: object[name], writable: descriptor.writable || false, configurable: descriptor.configurable || false, enumerable: descriptor.enumerable || false};
618                 if (possibleNativeBindingGetter)
619                     descriptor.nativeGetter = true;
620                 if (isOwnProperty)
621                     descriptor.isOwn = true;
622                 if (symbol)
623                     descriptor.symbol = symbol;
624                 return descriptor;
625             } catch (e) {
626                 var errorDescriptor = {name, value: e, wasThrown: true};
627                 if (isOwnProperty)
628                     errorDescriptor.isOwn = true;
629                 if (symbol)
630                     errorDescriptor.symbol = symbol;
631                 return errorDescriptor;
632             }
633         }
634
635         function processDescriptor(descriptor, isOwnProperty, possibleNativeBindingGetter)
636         {
637             // All properties.
638             if (collectionMode & InjectedScript.CollectionMode.AllProperties) {
639                 descriptors.push(descriptor);
640                 return;
641             }
642
643             // Own properties.
644             if (collectionMode & InjectedScript.CollectionMode.OwnProperties && isOwnProperty) {
645                 descriptors.push(descriptor);
646                 return;
647             }
648
649             // Native Getter properties.
650             if (collectionMode & InjectedScript.CollectionMode.NativeGetterProperties) {
651                 // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete
652                 // if (descriptor.hasOwnProperty("get") && descriptor.get && isNativeFunction(descriptor.get)) { ... }
653
654                 if (possibleNativeBindingGetter) {
655                     // Possible getter property in the prototype chain.
656                     descriptors.push(descriptor);
657                     return;
658                 }
659             }
660         }
661
662         function processProperties(o, properties, isOwnProperty)
663         {
664             for (var i = 0; i < properties.length; ++i) {
665                 var property = properties[i];
666                 if (nameProcessed.has(property) || property === "__proto__")
667                     continue;
668
669                 nameProcessed.add(property);
670
671                 var name = toString(property);
672                 var symbol = isSymbol(property) ? property : null;
673
674                 var descriptor = Object.getOwnPropertyDescriptor(o, property);
675                 if (!descriptor) {
676                     // FIXME: Bad descriptor. Can we get here?
677                     // Fall back to very restrictive settings.
678                     var fakeDescriptor = createFakeValueDescriptor(name, symbol, {writable: false, configurable: false, enumerable: false}, isOwnProperty);
679                     processDescriptor(fakeDescriptor, isOwnProperty);
680                     continue;
681                 }
682
683                 if (descriptor.hasOwnProperty("get") && descriptor.hasOwnProperty("set") && !descriptor.get && !descriptor.set) {
684                     // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete
685                     // Developers may create such a descriptors, so we should be resilient:
686                     // var x = {}; Object.defineProperty(x, "p", {get:undefined}); Object.getOwnPropertyDescriptor(x, "p")
687                     var fakeDescriptor = createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty, true);
688                     processDescriptor(fakeDescriptor, isOwnProperty, true);
689                     continue;
690                 }
691
692                 descriptor.name = name;
693                 if (isOwnProperty)
694                     descriptor.isOwn = true;
695                 if (symbol)
696                     descriptor.symbol = symbol;
697                 processDescriptor(descriptor, isOwnProperty);
698             }
699         }
700
701         // Iterate prototype chain.
702         for (var o = object; this._isDefined(o); o = o.__proto__) {
703             var isOwnProperty = o === object;
704             processProperties(o, Object.getOwnPropertyNames(o), isOwnProperty);
705             if (Object.getOwnPropertySymbols)
706                 processProperties(o, Object.getOwnPropertySymbols(o), isOwnProperty);
707             if (collectionMode === InjectedScript.CollectionMode.OwnProperties)
708                 break;
709         }
710
711         // Always include __proto__ at the end.
712         try {
713             if (object.__proto__)
714                 descriptors.push({name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true});
715         } catch (e) {}
716
717         return descriptors;
718     },
719
720     _isDefined: function(object)
721     {
722         return !!object || this._isHTMLAllCollection(object);
723     },
724
725     _isHTMLAllCollection: function(object)
726     {
727         // document.all is reported as undefined, but we still want to process it.
728         return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object);
729     },
730
731     _subtype: function(obj)
732     {
733         if (obj === null)
734             return "null";
735
736         if (this.isPrimitiveValue(obj) || isSymbol(obj))
737             return null;
738
739         if (this._isHTMLAllCollection(obj))
740             return "array";
741
742         var preciseType = InjectedScriptHost.subtype(obj);
743         if (preciseType)
744             return preciseType;
745
746         // FireBug's array detection.
747         try {
748             if (typeof obj.splice === "function" && isFinite(obj.length))
749                 return "array";
750             if (Object.prototype.toString.call(obj) === "[object Arguments]" && isFinite(obj.length)) // arguments.
751                 return "array";
752         } catch (e) {
753         }
754
755         return null;
756     },
757
758     _describe: function(obj)
759     {
760         if (this.isPrimitiveValue(obj))
761             return null;
762
763         if (isSymbol(obj))
764             return toString(obj);
765
766         var subtype = this._subtype(obj);
767
768         if (subtype === "regexp")
769             return toString(obj);
770
771         if (subtype === "date")
772             return toString(obj);
773
774         if (subtype === "error")
775             return toString(obj);
776
777         if (subtype === "node") {
778             var description = obj.nodeName.toLowerCase();
779             switch (obj.nodeType) {
780             case 1 /* Node.ELEMENT_NODE */:
781                 description += obj.id ? "#" + obj.id : "";
782                 var className = obj.className;
783                 description += (className && typeof className === "string") ? "." + className.trim().replace(/\s+/g, ".") : "";
784                 break;
785             case 10 /*Node.DOCUMENT_TYPE_NODE */:
786                 description = "<!DOCTYPE " + description + ">";
787                 break;
788             }
789             return description;
790         }
791
792         var className = InjectedScriptHost.internalConstructorName(obj);
793         if (subtype === "array")
794             return className;
795
796         if (subtype === "class")
797             return obj.name;
798
799         // NodeList in JSC is a function, check for array prior to this.
800         if (typeof obj === "function")
801             return toString(obj);
802
803         // If Object, try for a better name from the constructor.
804         if (className === "Object") {
805             var constructorName = obj.constructor && obj.constructor.name;
806             if (constructorName)
807                 return constructorName;
808         }
809
810         return className;
811     },
812
813     _getSetEntries: function(object, skip, numberToFetch)
814     {
815         var entries = [];
816
817         for (var value of object) {
818             if (skip > 0) {
819                 skip--;
820                 continue;
821             }
822
823             entries.push({value});
824
825             if (numberToFetch && entries.length === numberToFetch)
826                 break;
827         }
828
829         return entries;
830     },
831
832     _getMapEntries: function(object, skip, numberToFetch)
833     {
834         var entries = [];
835
836         for (var [key, value] of object) {
837             if (skip > 0) {
838                 skip--;
839                 continue;
840             }
841
842             entries.push({key, value});
843
844             if (numberToFetch && entries.length === numberToFetch)
845                 break;
846         }
847
848         return entries;
849     },
850
851     _getWeakMapEntries: function(object, numberToFetch)
852     {
853         return InjectedScriptHost.weakMapEntries(object, numberToFetch);
854     },
855
856     _getIteratorEntries: function(object, numberToFetch)
857     {
858         return InjectedScriptHost.iteratorEntries(object, numberToFetch);
859     },
860
861     _entries: function(object, subtype, startIndex, numberToFetch)
862     {
863         if (subtype === "set")
864             return this._getSetEntries(object, startIndex, numberToFetch);
865         if (subtype === "map")
866             return this._getMapEntries(object, startIndex, numberToFetch);
867         if (subtype === "weakmap")
868             return this._getWeakMapEntries(object, numberToFetch);
869         if (subtype === "iterator")
870             return this._getIteratorEntries(object, numberToFetch);
871
872         throw "unexpected type";
873     },
874
875     _saveResult: function(result)
876     {
877         this._lastResult = result;
878
879         if (result === undefined || result === null)
880             return;
881
882         var existingIndex = this._savedResults.indexOf(result);
883         if (existingIndex !== -1) {
884             this._savedResultIndex = existingIndex;
885             return;
886         }
887
888         this._savedResultIndex = this._nextSavedResultIndex;
889         this._savedResults[this._nextSavedResultIndex++] = result;
890
891         // $n is limited from $1-$99. $0 is special.
892         if (this._nextSavedResultIndex >= 100)
893             this._nextSavedResultIndex = 1;
894     },
895
896     _savedResult: function(index)
897     {
898         return this._savedResults[index];
899     }
900 }
901
902 var injectedScript = new InjectedScript;
903
904
905 InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames)
906 {
907     this.type = typeof object;
908
909     if (this.type === "undefined" && injectedScript._isHTMLAllCollection(object))
910         this.type = "object";
911
912     if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
913         // We don't send undefined values over JSON.
914         if (this.type !== "undefined")
915             this.value = object;
916
917         // Null object is object with 'null' subtype.
918         if (object === null)
919             this.subtype = "null";
920
921         // Provide user-friendly number values.
922         if (this.type === "number")
923             this.description = toStringDescription(object);
924         return;
925     }
926
927     this.objectId = injectedScript._bind(object, objectGroupName);
928
929     var subtype = injectedScript._subtype(object);
930     if (subtype)
931         this.subtype = subtype;
932
933     this.className = InjectedScriptHost.internalConstructorName(object);
934     this.description = injectedScript._describe(object);
935
936     if (subtype === "array")
937         this.size = typeof object.length === "number" ? object.length : 0;
938     else if (subtype === "set" || subtype === "map")
939         this.size = object.size;
940     else if (subtype === "weakmap")
941         this.size = InjectedScriptHost.weakMapSize(object);
942     else if (subtype === "class")
943         this.classPrototype = injectedScript._wrapObject(object.prototype, objectGroupName);
944
945     if (generatePreview && this.type === "object")
946         this.preview = this._generatePreview(object, undefined, columnNames);
947 }
948
949 InjectedScript.RemoteObject.prototype = {
950     _emptyPreview: function()
951     {
952         var preview = {
953             type: this.type,
954             description: this.description || toString(this.value),
955             lossless: true,
956         };
957
958         if (this.subtype) {
959             preview.subtype = this.subtype;
960             if (this.subtype !== "null") {
961                 preview.overflow = false;
962                 preview.properties = [];
963             }
964         }
965
966         if ("size" in this)
967             preview.size = this.size;
968
969         return preview;
970     },
971
972     _createObjectPreviewForValue: function(value)
973     {
974         var remoteObject = new InjectedScript.RemoteObject(value, undefined, false, true, undefined);
975         if (remoteObject.objectId)
976             injectedScript.releaseObject(remoteObject.objectId);
977         if (remoteObject.classPrototype && remoteObject.classPrototype.objectId)
978             injectedScript.releaseObject(remoteObject.classPrototype.objectId);
979
980         return remoteObject.preview || remoteObject._emptyPreview();
981     },
982
983     _generatePreview: function(object, firstLevelKeys, secondLevelKeys)
984     {
985         var preview = this._emptyPreview();
986
987         // Primitives just have a value.
988         if (this.type !== "object")
989             return;
990
991         var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys;
992         var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
993
994         var propertiesThreshold = {
995             properties: isTableRowsRequest ? 1000 : Math.max(5, firstLevelKeysCount),
996             indexes: isTableRowsRequest ? 1000 : Math.max(100, firstLevelKeysCount)
997         };
998
999         try {
1000             // Maps, Sets, and Iterators have entries.
1001             if (this.subtype === "map" || this.subtype === "set" || this.subtype === "weakmap" || this.subtype === "iterator")
1002                 this._appendEntryPreviews(object, preview);
1003
1004             preview.properties = [];
1005
1006             // Internal Properties.
1007             var internalPropertyDescriptors = injectedScript._internalPropertyDescriptors(object, true);
1008             if (internalPropertyDescriptors) {
1009                 this._appendPropertyPreviews(preview, internalPropertyDescriptors, true, propertiesThreshold, firstLevelKeys, secondLevelKeys);
1010                 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
1011                     return preview;
1012             }
1013
1014             if (preview.entries)
1015                 return preview;
1016
1017             // Properties.
1018             var descriptors = injectedScript._propertyDescriptors(object, InjectedScript.CollectionMode.AllProperties);
1019             this._appendPropertyPreviews(preview, descriptors, false, propertiesThreshold, firstLevelKeys, secondLevelKeys);
1020             if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
1021                 return preview;
1022         } catch (e) {
1023             preview.lossless = false;
1024         }
1025
1026         return preview;
1027     },
1028
1029     _appendPropertyPreviews: function(preview, descriptors, internal, propertiesThreshold, firstLevelKeys, secondLevelKeys)
1030     {
1031         for (var descriptor of descriptors) {
1032             // Seen enough.
1033             if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
1034                 break;
1035
1036             // Error in descriptor.
1037             if (descriptor.wasThrown) {
1038                 preview.lossless = false;
1039                 continue;
1040             }
1041
1042             // Do not show "__proto__" in preview.
1043             var name = descriptor.name;
1044             if (name === "__proto__")
1045                 continue;
1046
1047             // Do not show "length" on array like objects in preview.
1048             if (this.subtype === "array" && name === "length")
1049                 continue;
1050
1051             // Do not show non-enumerable non-own properties. Special case to allow array indexes that may be on the prototype.
1052             if (!descriptor.enumerable && !descriptor.isOwn && !(this.subtype === "array" && isUInt32(name)))
1053                 continue;
1054
1055             // If we have a filter, only show properties in the filter.
1056             if (firstLevelKeys && firstLevelKeys.indexOf(name) === -1)
1057                 continue;
1058
1059             // Getter/setter.
1060             if (!("value" in descriptor)) {
1061                 preview.lossless = false;
1062                 this._appendPropertyPreview(preview, internal, {name, type: "accessor"}, propertiesThreshold);
1063                 continue;
1064             }
1065
1066             // Null value.
1067             var value = descriptor.value;
1068             if (value === null) {
1069                 this._appendPropertyPreview(preview, internal, {name, type: "object", subtype: "null", value: "null"}, propertiesThreshold);
1070                 continue;
1071             }
1072
1073             // Ignore non-enumerable functions.
1074             var type = typeof value;
1075             if (!descriptor.enumerable && type === "function")
1076                 continue;
1077
1078             // Fix type of document.all.
1079             if (type === "undefined" && injectedScript._isHTMLAllCollection(value))
1080                 type = "object";
1081
1082             // Primitive.
1083             const maxLength = 100;
1084             if (InjectedScript.primitiveTypes[type]) {
1085                 if (type === "string" && value.length > maxLength) {
1086                     value = this._abbreviateString(value, maxLength, true);
1087                     preview.lossless = false;
1088                 }
1089                 this._appendPropertyPreview(preview, internal, {name, type, value: toStringDescription(value)}, propertiesThreshold);
1090                 continue;
1091             }
1092
1093             // Symbol.
1094             if (isSymbol(value)) {
1095                 var symbolString = toString(value);
1096                 if (symbolString.length > maxLength) {
1097                     symbolString = this._abbreviateString(symbolString, maxLength, true);
1098                     preview.lossless = false;
1099                 }
1100                 this._appendPropertyPreview(preview, internal, {name, type, value: symbolString}, propertiesThreshold);
1101                 return;
1102             }
1103
1104             // Object.
1105             var property = {name, type};
1106             var subtype = injectedScript._subtype(value);
1107             if (subtype)
1108                 property.subtype = subtype;
1109
1110             // Second level.
1111             if (secondLevelKeys === null || secondLevelKeys) {
1112                 var subPreview = this._generatePreview(value, secondLevelKeys || undefined, undefined);
1113                 property.valuePreview = subPreview;
1114                 if (!subPreview.lossless)
1115                     preview.lossless = false;
1116                 if (subPreview.overflow)
1117                     preview.overflow = true;
1118             } else if (this._isPreviewableObject(value)) {
1119                 var subPreview = this._createObjectPreviewForValue(value);
1120                 property.valuePreview = subPreview;
1121                 if (!subPreview.lossless)
1122                     preview.lossless = false;
1123                 if (subPreview.overflow)
1124                     preview.overflow = true;
1125             } else {
1126                 var description = "";
1127                 if (type !== "function" || subtype === "class")
1128                     description = this._abbreviateString(injectedScript._describe(value), maxLength, subtype === "regexp");
1129                 property.value = description;
1130                 preview.lossless = false;
1131             }
1132
1133             this._appendPropertyPreview(preview, internal, property, propertiesThreshold);
1134         }
1135     },
1136
1137     _appendPropertyPreview: function(preview, internal, property, propertiesThreshold)
1138     {
1139         if (toString(property.name >>> 0) === property.name)
1140             propertiesThreshold.indexes--;
1141         else
1142             propertiesThreshold.properties--;
1143
1144         if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) {
1145             preview.overflow = true;
1146             preview.lossless = false;
1147             return;
1148         }
1149
1150         if (internal)
1151             property.internal = true;
1152
1153         preview.properties.push(property);
1154     },
1155
1156     _appendEntryPreviews: function(object, preview)
1157     {
1158         // Fetch 6, but only return 5, so we can tell if we overflowed.
1159         var entries = injectedScript._entries(object, this.subtype, 0, 6);
1160         if (!entries)
1161             return;
1162
1163         if (entries.length > 5) {
1164             entries.pop();
1165             preview.overflow = true;
1166             preview.lossless = false;
1167         }
1168
1169         preview.entries = entries.map(function(entry) {
1170             entry.value = this._createObjectPreviewForValue(entry.value);
1171             if ("key" in entry)
1172                 entry.key = this._createObjectPreviewForValue(entry.key);
1173             return entry;
1174         }, this);
1175     },
1176
1177     _isPreviewableObject: function(object)
1178     {
1179         return this._isPreviewableObjectInternal(object, new Set, 1);
1180     },
1181
1182     _isPreviewableObjectInternal: function(object, knownObjects, depth)
1183     {
1184         // Deep object.
1185         if (depth > 3)
1186             return false;
1187
1188         // Primitive.
1189         if (injectedScript.isPrimitiveValue(object) || isSymbol(object))
1190             return true;
1191
1192         // Null.
1193         if (object === null)
1194             return true;
1195
1196         // Cyclic objects.
1197         if (knownObjects.has(object))
1198             return false;
1199
1200         ++depth;
1201         knownObjects.add(object);
1202
1203         // Arrays are simple if they have 5 or less simple objects.
1204         var subtype = injectedScript._subtype(object);
1205         if (subtype === "array") {
1206             var length = object.length;
1207             if (length > 5)
1208                 return false;
1209             for (var i = 0; i < length; ++i) {
1210                 if (!this._isPreviewableObjectInternal(object[i], knownObjects, depth))
1211                     return false;
1212             }
1213             return true;
1214         }
1215
1216         // Not a basic object.
1217         if (object.__proto__ && object.__proto__.__proto__)
1218             return false;
1219
1220         // Objects are simple if they have 3 or less simple properties.
1221         var ownPropertyNames = Object.getOwnPropertyNames(object);
1222         if (ownPropertyNames.length > 3)
1223             return false;
1224         for (var propertyName of ownPropertyNames) {
1225             if (!this._isPreviewableObjectInternal(object[propertyName], knownObjects, depth))
1226                 return false;
1227         }
1228
1229         return true;
1230     },
1231
1232     _abbreviateString: function(string, maxLength, middle)
1233     {
1234         if (string.length <= maxLength)
1235             return string;
1236
1237         if (middle) {
1238             var leftHalf = maxLength >> 1;
1239             var rightHalf = maxLength - leftHalf - 1;
1240             return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf);
1241         }
1242
1243         return string.substr(0, maxLength) + "\u2026";
1244     }
1245 }
1246
1247 InjectedScript.CallFrameProxy = function(ordinal, callFrame)
1248 {
1249     this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}";
1250     this.functionName = (callFrame.type === "function" ? callFrame.functionName : "");
1251     this.location = {scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column};
1252     this.scopeChain = this._wrapScopeChain(callFrame);
1253     this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace");
1254 }
1255
1256 InjectedScript.CallFrameProxy.prototype = {
1257     _wrapScopeChain: function(callFrame)
1258     {
1259         var scopeChain = callFrame.scopeChain;
1260         var scopeChainProxy = [];
1261         for (var i = 0; i < scopeChain.length; i++)
1262             scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace");
1263         return scopeChainProxy;
1264     }
1265 }
1266
1267 InjectedScript.CallFrameProxy._scopeTypeNames = {
1268     0: "global", // GLOBAL_SCOPE
1269     1: "local", // LOCAL_SCOPE
1270     2: "with", // WITH_SCOPE
1271     3: "closure", // CLOSURE_SCOPE
1272     4: "catch", // CATCH_SCOPE
1273     5: "functionName", // FUNCTION_NAME_SCOPE
1274 }
1275
1276 InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId)
1277 {
1278     return {
1279         object: injectedScript._wrapObject(scopeObject, groupId),
1280         type: InjectedScript.CallFrameProxy._scopeTypeNames[scopeTypeCode]
1281     };
1282 }
1283
1284
1285 function slice(array, index)
1286 {
1287     var result = [];
1288     for (var i = index || 0; i < array.length; ++i)
1289         result.push(array[i]);
1290     return result;
1291 }
1292
1293 function bind(func, thisObject, var_args)
1294 {
1295     var args = slice(arguments, 2);
1296     return function(var_args) {
1297         return func.apply(thisObject, args.concat(slice(arguments)));
1298     }
1299 }
1300
1301 function BasicCommandLineAPI()
1302 {
1303     this.$_ = injectedScript._lastResult;
1304     this.$exception = injectedScript._exceptionValue;
1305
1306     // $1-$99
1307     for (var i = 1; i <= injectedScript._savedResults.length; ++i) {
1308         var member = "$" + i;
1309         if (member in inspectedGlobalObject)
1310             continue;
1311         this.__defineGetter__("$" + i, bind(injectedScript._savedResult, injectedScript, i));
1312     }
1313 }
1314
1315 return injectedScript;
1316 })