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