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