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