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