Web Inspector: ASSERT expanding objects in console PrimitiveBindingTraits<T>::assertV...
[WebKit-https.git] / Source / JavaScriptCore / inspector / InjectedScriptSource.js
1 /*
2  * Copyright (C) 2007, 2014 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 //# sourceURL=__WebInspectorInjectedScript__
30
31 (function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) {
32
33 // Protect against Object overwritten by the user code.
34 var Object = {}.constructor;
35
36 var InjectedScript = function()
37 {
38     this._lastBoundObjectId = 1;
39     this._idToWrappedObject = {};
40     this._idToObjectGroupName = {};
41     this._objectGroups = {};
42     this._modules = {};
43 }
44
45 InjectedScript.primitiveTypes = {
46     undefined: true,
47     boolean: true,
48     number: true,
49     string: true
50 }
51
52 InjectedScript.prototype = {
53     isPrimitiveValue: function(object)
54     {
55         // FIXME(33716): typeof document.all is always 'undefined'.
56         return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
57     },
58
59     wrapObject: function(object, groupName, canAccessInspectedGlobalObject, generatePreview)
60     {
61         if (canAccessInspectedGlobalObject)
62             return this._wrapObject(object, groupName, false, generatePreview);
63         return this._fallbackWrapper(object);
64     },
65
66     setExceptionValue: function(value)
67     {
68         this._exceptionValue = value;
69     },
70
71     clearExceptionValue: function()
72     {
73         delete this._exceptionValue;
74     },
75
76     _fallbackWrapper: function(object)
77     {
78         var result = {};
79         result.type = typeof object;
80         if (this.isPrimitiveValue(object))
81             result.value = object;
82         else
83             result.description = this._toString(object);
84         return result;
85     },
86
87     wrapTable: function(canAccessInspectedGlobalObject, table, columns)
88     {
89         if (!canAccessInspectedGlobalObject)
90             return this._fallbackWrapper(table);
91         var columnNames = null;
92         if (typeof columns === "string")
93             columns = [columns];
94         if (InjectedScriptHost.type(columns) === "array") {
95             columnNames = [];
96             for (var i = 0; i < columns.length; ++i)
97                 columnNames.push(String(columns[i]));
98         }
99         return this._wrapObject(table, "console", false, true, columnNames);
100     },
101
102     inspectObject: function(object)
103     {
104         if (this._commandLineAPIImpl)
105             this._commandLineAPIImpl.inspect(object);
106     },
107
108     _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames)
109     {
110         try {
111             return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType, generatePreview, columnNames);
112         } catch (e) {
113             try {
114                 var description = injectedScript._describe(e);
115             } catch (ex) {
116                 var description = "<failed to convert exception to string>";
117             }
118             return new InjectedScript.RemoteObject(description);
119         }
120     },
121
122     _bind: function(object, objectGroupName)
123     {
124         var id = this._lastBoundObjectId++;
125         this._idToWrappedObject[id] = object;
126         var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}";
127         if (objectGroupName) {
128             var group = this._objectGroups[objectGroupName];
129             if (!group) {
130                 group = [];
131                 this._objectGroups[objectGroupName] = group;
132             }
133             group.push(id);
134             this._idToObjectGroupName[id] = objectGroupName;
135         }
136         return objectId;
137     },
138
139     _parseObjectId: function(objectId)
140     {
141         return InjectedScriptHost.evaluate("(" + objectId + ")");
142     },
143
144     releaseObjectGroup: function(objectGroupName)
145     {
146         if (objectGroupName === "console")
147             delete this._lastResult;
148         var group = this._objectGroups[objectGroupName];
149         if (!group)
150             return;
151         for (var i = 0; i < group.length; i++)
152             this._releaseObject(group[i]);
153         delete this._objectGroups[objectGroupName];
154     },
155
156     dispatch: function(methodName, args)
157     {
158         var argsArray = InjectedScriptHost.evaluate("(" + args + ")");
159         var result = this[methodName].apply(this, argsArray);
160         if (typeof result === "undefined") {
161             if (inspectedGlobalObject.console)
162                 inspectedGlobalObject.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
163             result = null;
164         }
165         return result;
166     },
167
168     getProperties: function(objectId, ownProperties, ownAndGetterProperties)
169     {
170         var parsedObjectId = this._parseObjectId(objectId);
171         var object = this._objectForId(parsedObjectId);
172         var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
173
174         if (!this._isDefined(object))
175             return false;
176
177         var descriptors = this._propertyDescriptors(object, ownProperties, ownAndGetterProperties);
178
179         // Go over properties, wrap object values.
180         for (var i = 0; i < descriptors.length; ++i) {
181             var descriptor = descriptors[i];
182             if ("get" in descriptor)
183                 descriptor.get = this._wrapObject(descriptor.get, objectGroupName);
184             if ("set" in descriptor)
185                 descriptor.set = this._wrapObject(descriptor.set, objectGroupName);
186             if ("value" in descriptor)
187                 descriptor.value = this._wrapObject(descriptor.value, objectGroupName);
188             if (!("configurable" in descriptor))
189                 descriptor.configurable = false;
190             if (!("enumerable" in descriptor))
191                 descriptor.enumerable = false;
192         }
193
194         return descriptors;
195     },
196
197     getInternalProperties: function(objectId, ownProperties)
198     {
199         var parsedObjectId = this._parseObjectId(objectId);
200         var object = this._objectForId(parsedObjectId);
201         var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
202         if (!this._isDefined(object))
203             return false;
204
205         var descriptors = [];
206         var internalProperties = InjectedScriptHost.getInternalProperties(object);
207         if (internalProperties) {
208             for (var i = 0; i < internalProperties.length; i++) {
209                 var property = internalProperties[i];
210                 var descriptor = {
211                     name: property.name,
212                     value: this._wrapObject(property.value, objectGroupName)
213                 };
214                 descriptors.push(descriptor);
215             }
216         }
217
218         return descriptors;
219     },
220
221     getFunctionDetails: function(functionId)
222     {
223         var parsedFunctionId = this._parseObjectId(functionId);
224         var func = this._objectForId(parsedFunctionId);
225         if (typeof func !== "function")
226             return "Cannot resolve function by id.";
227         var details = InjectedScriptHost.functionDetails(func);
228         if (!details)
229             return "Cannot resolve function details.";
230         if ("rawScopes" in details) {
231             var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id];
232             var rawScopes = details.rawScopes;
233             var scopes = [];
234             delete details.rawScopes;
235             for (var i = 0; i < rawScopes.length; i++)
236                 scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName));
237             details.scopeChain = scopes;
238         }
239         return details;
240     },
241
242     releaseObject: function(objectId)
243     {
244         var parsedObjectId = this._parseObjectId(objectId);
245         this._releaseObject(parsedObjectId.id);
246     },
247
248     _releaseObject: function(id)
249     {
250         delete this._idToWrappedObject[id];
251         delete this._idToObjectGroupName[id];
252     },
253
254     evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview)
255     {
256         return this._evaluateAndWrap(InjectedScriptHost.evaluate, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview);
257     },
258
259     callFunctionOn: function(objectId, expression, args, returnByValue)
260     {
261         var parsedObjectId = this._parseObjectId(objectId);
262         var object = this._objectForId(parsedObjectId);
263         if (!this._isDefined(object))
264             return "Could not find object with given id";
265
266         if (args) {
267             var resolvedArgs = [];
268             var callArgs = InjectedScriptHost.evaluate(args);
269             for (var i = 0; i < callArgs.length; ++i) {
270                 try {
271                     resolvedArgs[i] = this._resolveCallArgument(callArgs[i]);
272                 } catch (e) {
273                     return String(e);
274                 }
275             }
276         }
277
278         try {
279             var objectGroup = this._idToObjectGroupName[parsedObjectId.id];
280             var func = InjectedScriptHost.evaluate("(" + expression + ")");
281             if (typeof func !== "function")
282                 return "Given expression does not evaluate to a function";
283
284             return {
285                 wasThrown: false,
286                 result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue)
287             };
288         } catch (e) {
289             return this._createThrownValue(e, objectGroup);
290         }
291     },
292
293     _resolveCallArgument: function(callArgumentJson) {
294         var objectId = callArgumentJson.objectId;
295         if (objectId) {
296             var parsedArgId = this._parseObjectId(objectId);
297             if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId)
298                 throw "Arguments should belong to the same JavaScript world as the target object.";
299
300             var resolvedArg = this._objectForId(parsedArgId);
301             if (!this._isDefined(resolvedArg))
302                 throw "Could not find object with given id";
303
304             return resolvedArg;
305         } else if ("value" in callArgumentJson)
306             return callArgumentJson.value;
307         return undefined;
308     },
309
310     _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview)
311     {
312         try {
313             return {
314                 wasThrown: false,
315                 result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI), objectGroup, returnByValue, generatePreview)
316             };
317         } catch (e) {
318             return this._createThrownValue(e, objectGroup);
319         }
320     },
321
322     _createThrownValue: function(value, objectGroup)
323     {
324         var remoteObject = this._wrapObject(value, objectGroup);
325         try {
326             remoteObject.description = this._toString(value);
327         } catch (e) {}
328         return {
329             wasThrown: true,
330             result: remoteObject
331         };
332     },
333
334     _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI)
335     {
336         var commandLineAPI = null;
337         if (injectCommandLineAPI) {
338             if (this.CommandLineAPI)
339                 commandLineAPI = new this.CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
340             else
341                 commandLineAPI = new BasicCommandLineAPI;
342         }
343
344         if (isEvalOnCallFrame) {
345             // We can only use this approach if the evaluate function is the true 'eval'. That allows us to use it with
346             // the 'eval' identifier when calling it. Using 'eval' grants access to the local scope of the closure we
347             // create that provides the command line APIs.
348
349             var parameters = [InjectedScriptHost.evaluate, expression];
350             var expressionFunctionBody = "" +
351                 "var global = Function('return this')() || (1, eval)('this');" +
352                 "var __originalEval = global.eval; global.eval = __eval;" +
353                 "try { return eval(__currentExpression); }" +
354                 "finally { global.eval = __originalEval; }";
355
356             if (commandLineAPI) {
357                 // To avoid using a 'with' statement (which fails in strict mode and requires injecting the API object)
358                 // we instead create a closure where we evaluate the expression. The command line APIs are passed as
359                 // parameters to the closure so they are in scope but not injected. This allows the code evaluated in
360                 // the console to stay in strict mode (if is was already set), or to get strict mode by prefixing
361                 // expressions with 'use strict';.
362
363                 var parameterNames = Object.getOwnPropertyNames(commandLineAPI);
364                 for (var i = 0; i < parameterNames.length; ++i)
365                     parameters.push(commandLineAPI[parameterNames[i]]);
366
367                 var expressionFunctionString = "(function(__eval, __currentExpression, " + parameterNames.join(", ") + ") { " + expressionFunctionBody + " })";
368             } else {
369                 // Use a closure in this case too to keep the same behavior of 'var' being captured by the closure instead
370                 // of leaking out into the calling scope.
371                 var expressionFunctionString = "(function(__eval, __currentExpression) { " + expressionFunctionBody + " })";
372             }
373
374             // Bind 'this' to the function expression using another closure instead of Function.prototype.bind. This ensures things will work if the page replaces bind.
375             var boundExpressionFunctionString = "(function(__function, __thisObject) { return function() { return __function.apply(__thisObject, arguments) }; })(" + expressionFunctionString + ", this)";
376             var expressionFunction = evalFunction.call(object, boundExpressionFunctionString);
377             var result = expressionFunction.apply(null, parameters);
378
379             if (objectGroup === "console")
380                 this._lastResult = result;
381
382             return result;
383         }
384
385         // When not evaluating on a call frame we use a 'with' statement to allow var and function statements to leak
386         // into the global scope. This allow them to stick around between evaluations.
387
388         try {
389             if (commandLineAPI) {
390                 if (inspectedGlobalObject.console)
391                     inspectedGlobalObject.console.__commandLineAPI = commandLineAPI;
392                 else
393                     inspectedGlobalObject.__commandLineAPI = commandLineAPI;
394                 expression = "with ((this && (this.console ? this.console.__commandLineAPI : this.__commandLineAPI)) || {}) { " + expression + "\n}";
395             }
396
397             var result = evalFunction.call(inspectedGlobalObject, expression);
398
399             if (objectGroup === "console")
400                 this._lastResult = result;
401
402             return result;
403         } finally {
404             if (commandLineAPI) {
405                 if (inspectedGlobalObject.console)
406                     delete inspectedGlobalObject.console.__commandLineAPI;
407                 else
408                     delete inspectedGlobalObject.__commandLineAPI;
409             }
410         }
411     },
412
413     wrapCallFrames: function(callFrame)
414     {
415         if (!callFrame)
416             return false;
417
418         var result = [];
419         var depth = 0;
420         do {
421             result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
422             callFrame = callFrame.caller;
423         } while (callFrame);
424         return result;
425     },
426
427     evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview)
428     {
429         var callFrame = this._callFrameForId(topCallFrame, callFrameId);
430         if (!callFrame)
431             return "Could not find call frame with given id";
432         return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview);
433     },
434
435     _callFrameForId: function(topCallFrame, callFrameId)
436     {
437         var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")");
438         var ordinal = parsedCallFrameId["ordinal"];
439         var callFrame = topCallFrame;
440         while (--ordinal >= 0 && callFrame)
441             callFrame = callFrame.caller;
442         return callFrame;
443     },
444
445     _objectForId: function(objectId)
446     {
447         return this._idToWrappedObject[objectId.id];
448     },
449
450     findObjectById: function(objectId)
451     {
452         var parsedObjectId = this._parseObjectId(objectId);
453         return this._objectForId(parsedObjectId);
454     },
455
456     module: function(name)
457     {
458         return this._modules[name];
459     },
460
461     injectModule: function(name, source, host)
462     {
463         delete this._modules[name];
464
465         var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")");
466         if (typeof moduleFunction !== "function") {
467             if (inspectedGlobalObject.console)
468                 inspectedGlobalObject.console.error("Web Inspector error: A function was expected for module %s evaluation", name);
469             return null;
470         }
471
472         var module = moduleFunction.call(inspectedGlobalObject, InjectedScriptHost, inspectedGlobalObject, injectedScriptId, this, host);
473         this._modules[name] = module;
474         return module;
475     },
476
477     _propertyDescriptors: function(object, ownProperties, ownAndGetterProperties)
478     {
479         // Modes:
480         //  - ownProperties - only own properties and __proto__
481         //  - ownAndGetterProperties - own properties, __proto__, and getters in the prototype chain
482         //  - neither - get all properties in the prototype chain, exclude __proto__
483
484         var descriptors = [];
485         var nameProcessed = {};
486         nameProcessed["__proto__"] = null;
487
488         function createFakeValueDescriptor(name, descriptor, isOwnProperty)
489         {
490             try {
491                 return {name: name, value: object[name], writable: descriptor.writable || false, configurable: descriptor.configurable || false, enumerable: descriptor.enumerable || false};
492             } catch (e) {
493                 var errorDescriptor = {name: name, value: e, wasThrown: true};
494                 if (isOwnProperty)
495                     errorDescriptor.isOwn = true;
496                 return errorDescriptor;
497             }
498         }
499
500         function processDescriptor(descriptor, isOwnProperty, possibleNativeBindingGetter)
501         {
502             // Own properties only.
503             if (ownProperties) {
504                 if (isOwnProperty)
505                     descriptors.push(descriptor);
506                 return;
507             }
508
509             // Own and getter properties.
510             if (ownAndGetterProperties) {
511                 if (isOwnProperty) {
512                     // Own property, include the descriptor as is.
513                     descriptors.push(descriptor);
514                 } else if (descriptor.hasOwnProperty("get") && descriptor.get) {
515                     // Getter property in the prototype chain. Create a fake value descriptor.
516                     descriptors.push(createFakeValueDescriptor(descriptor.name, descriptor, isOwnProperty));
517                 } else if (possibleNativeBindingGetter) {
518                     // Possible getter property in the prototype chain.
519                     descriptors.push(descriptor);
520                 }
521                 return;
522             }
523
524             // All properties.
525             descriptors.push(descriptor);
526         }
527
528         function processPropertyNames(o, names, isOwnProperty)
529         {
530             for (var i = 0; i < names.length; ++i) {
531                 var name = names[i];
532                 if (nameProcessed[name] || name === "__proto__")
533                     continue;
534
535                 nameProcessed[name] = true;
536
537                 var descriptor = Object.getOwnPropertyDescriptor(o, name);
538                 if (!descriptor) {
539                     // FIXME: Bad descriptor. Can we get here?
540                     // Fall back to very restrictive settings.
541                     var fakeDescriptor = createFakeValueDescriptor(name, {writable: false, configurable: false, enumerable: false}, isOwnProperty);
542                     processDescriptor(fakeDescriptor, isOwnProperty);
543                     continue;
544                 }
545
546                 if (descriptor.hasOwnProperty("get") && descriptor.hasOwnProperty("set") && !descriptor.get && !descriptor.set) {
547                     // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete
548                     // Developers may create such a descriptors, so we should be resilient:
549                     // var x = {}; Object.defineProperty(x, "p", {get:undefined}); Object.getOwnPropertyDescriptor(x, "p")
550                     var fakeDescriptor = createFakeValueDescriptor(name, descriptor, isOwnProperty);
551                     processDescriptor(fakeDescriptor, isOwnProperty, true);
552                     continue;
553                 }
554
555                 descriptor.name = name;
556                 if (isOwnProperty)
557                     descriptor.isOwn = true;
558                 processDescriptor(descriptor, isOwnProperty);
559             }
560         }
561
562         // Iterate prototype chain.
563         for (var o = object; this._isDefined(o); o = o.__proto__) {
564             var isOwnProperty = o === object;
565             processPropertyNames(o, Object.getOwnPropertyNames(o), isOwnProperty);
566             if (ownProperties)
567                 break;
568         }
569         
570         // Include __proto__ at the end.
571         try {
572             if (object.__proto__)
573                 descriptors.push({name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true});
574         } catch (e) {}
575
576         return descriptors;
577     },
578
579     _isDefined: function(object)
580     {
581         return !!object || this._isHTMLAllCollection(object);
582     },
583
584     _isHTMLAllCollection: function(object)
585     {
586         // document.all is reported as undefined, but we still want to process it.
587         return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object);
588     },
589
590     _subtype: function(obj)
591     {
592         if (obj === null)
593             return "null";
594
595         if (this.isPrimitiveValue(obj))
596             return null;
597
598         if (this._isHTMLAllCollection(obj))
599             return "array";
600
601         var preciseType = InjectedScriptHost.type(obj);
602         if (preciseType)
603             return preciseType;
604
605         // FireBug's array detection.
606         try {
607             if (typeof obj.splice === "function" && isFinite(obj.length))
608                 return "array";
609             if (Object.prototype.toString.call(obj) === "[object Arguments]" && isFinite(obj.length)) // arguments.
610                 return "array";
611         } catch (e) {
612         }
613
614         // If owning frame has navigated to somewhere else window properties will be undefined.
615         return null;
616     },
617
618     _describe: function(obj)
619     {
620         if (this.isPrimitiveValue(obj))
621             return null;
622
623         var subtype = this._subtype(obj);
624
625         if (subtype === "regexp")
626             return this._toString(obj);
627
628         if (subtype === "date")
629             return this._toString(obj);
630
631         if (subtype === "error")
632             return this._toString(obj);
633
634         if (subtype === "node") {
635             var description = obj.nodeName.toLowerCase();
636             switch (obj.nodeType) {
637             case 1 /* Node.ELEMENT_NODE */:
638                 description += obj.id ? "#" + obj.id : "";
639                 var className = obj.className;
640                 description += (className && typeof className === "string") ? "." + className.trim().replace(/\s+/g, ".") : "";
641                 break;
642             case 10 /*Node.DOCUMENT_TYPE_NODE */:
643                 description = "<!DOCTYPE " + description + ">";
644                 break;
645             }
646             return description;
647         }
648
649         var className = InjectedScriptHost.internalConstructorName(obj);
650         if (subtype === "array") {
651             if (typeof obj.length === "number")
652                 className += "[" + obj.length + "]";
653             return className;
654         }
655
656         // NodeList in JSC is a function, check for array prior to this.
657         if (typeof obj === "function")
658             return this._toString(obj);
659
660         // FIXME: Can we remove this?
661         if (className === "Object") {
662             // In Chromium DOM wrapper prototypes will have Object as their constructor name,
663             // get the real DOM wrapper name from the constructor property.
664             var constructorName = obj.constructor && obj.constructor.name;
665             if (constructorName)
666                 return constructorName;
667         }
668         return className;
669     },
670
671     _toString: function(obj)
672     {
673         // We don't use String(obj) because inspectedGlobalObject.String is undefined if owning frame navigated to another page.
674         return "" + obj;
675     }
676 }
677
678 var injectedScript = new InjectedScript;
679
680
681 InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames)
682 {
683     this.type = typeof object;
684
685     if (this.type === "undefined" && injectedScript._isHTMLAllCollection(object))
686         this.type = "object";
687
688     if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
689         // We don't send undefined values over JSON.
690         if (this.type !== "undefined")
691             this.value = object;
692
693         // Null object is object with 'null' subtype'
694         if (object === null)
695             this.subtype = "null";
696
697         // Provide user-friendly number values.
698         if (this.type === "number")
699             this.description = object + "";
700         return;
701     }
702
703     this.objectId = injectedScript._bind(object, objectGroupName);
704     var subtype = injectedScript._subtype(object);
705     if (subtype)
706         this.subtype = subtype;
707
708     this.className = InjectedScriptHost.internalConstructorName(object);
709     this.description = injectedScript._describe(object);
710
711     if (generatePreview && (this.type === "object" || injectedScript._isHTMLAllCollection(object)))
712         this.preview = this._generatePreview(object, undefined, columnNames);
713 }
714
715 InjectedScript.RemoteObject.prototype = {
716     _generatePreview: function(object, firstLevelKeys, secondLevelKeys)
717     {
718         var preview = {};
719         preview.lossless = true;
720         preview.overflow = false;
721         preview.properties = [];
722
723         var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys;
724         var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
725
726         var propertiesThreshold = {
727             properties: isTableRowsRequest ? 1000 : Math.max(5, firstLevelKeysCount),
728             indexes: isTableRowsRequest ? 1000 : Math.max(100, firstLevelKeysCount)
729         };
730         for (var o = object; injectedScript._isDefined(o); o = o.__proto__)
731             this._generateProtoPreview(o, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys);
732         return preview;
733     },
734
735     _generateProtoPreview: function(object, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys)
736     {
737         var propertyNames = firstLevelKeys ? firstLevelKeys : Object.keys(object);
738         try {
739             for (var i = 0; i < propertyNames.length; ++i) {
740                 if (!propertiesThreshold.properties || !propertiesThreshold.indexes) {
741                     preview.overflow = true;
742                     preview.lossless = false;
743                     break;
744                 }
745                 var name = propertyNames[i];
746                 if (this.subtype === "array" && name === "length")
747                     continue;
748
749                 var descriptor = Object.getOwnPropertyDescriptor(object, name);
750                 if (!("value" in descriptor) || !descriptor.enumerable) {
751                     preview.lossless = false;
752                     continue;
753                 }
754
755                 var value = descriptor.value;
756                 if (value === null) {
757                     this._appendPropertyPreview(preview, { name: name, type: "object", value: "null" }, propertiesThreshold);
758                     continue;
759                 }
760
761                 const maxLength = 100;
762                 var type = typeof value;
763
764                 if (InjectedScript.primitiveTypes[type]) {
765                     if (type === "string") {
766                         if (value.length > maxLength) {
767                             value = this._abbreviateString(value, maxLength, true);
768                             preview.lossless = false;
769                         }
770                         value = value.replace(/\n/g, "\u21B5");
771                     }
772                     this._appendPropertyPreview(preview, { name: name, type: type, value: value + "" }, propertiesThreshold);
773                     continue;
774                 }
775
776                 if (secondLevelKeys === null || secondLevelKeys) {
777                     var subPreview = this._generatePreview(value, secondLevelKeys || undefined);
778                     var property = { name: name, type: type, valuePreview: subPreview };
779                     this._appendPropertyPreview(preview, property, propertiesThreshold);
780                     if (!subPreview.lossless)
781                         preview.lossless = false;
782                     if (subPreview.overflow)
783                         preview.overflow = true;
784                     continue;
785                 }
786
787                 preview.lossless = false;
788
789                 var subtype = injectedScript._subtype(value);
790                 var description = "";
791                 if (type !== "function")
792                     description = this._abbreviateString(injectedScript._describe(value), maxLength, subtype === "regexp");
793
794                 var property = { name: name, type: type, value: description };
795                 if (subtype)
796                     property.subtype = subtype;
797                 this._appendPropertyPreview(preview, property, propertiesThreshold);
798             }
799         } catch (e) {
800         }
801     },
802
803     _appendPropertyPreview: function(preview, property, propertiesThreshold)
804     {
805         if (isNaN(property.name))
806             propertiesThreshold.properties--;
807         else
808             propertiesThreshold.indexes--;
809         preview.properties.push(property);
810     },
811
812     _abbreviateString: function(string, maxLength, middle)
813     {
814         if (string.length <= maxLength)
815             return string;
816
817         if (middle) {
818             var leftHalf = maxLength >> 1;
819             var rightHalf = maxLength - leftHalf - 1;
820             return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf);
821         }
822
823         return string.substr(0, maxLength) + "\u2026";
824     }
825 }
826
827 InjectedScript.CallFrameProxy = function(ordinal, callFrame)
828 {
829     this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}";
830     this.functionName = (callFrame.type === "function" ? callFrame.functionName : "");
831     this.location = {scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column};
832     this.scopeChain = this._wrapScopeChain(callFrame);
833     this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace");
834 }
835
836 InjectedScript.CallFrameProxy.prototype = {
837     _wrapScopeChain: function(callFrame)
838     {
839         var scopeChain = callFrame.scopeChain;
840         var scopeChainProxy = [];
841         for (var i = 0; i < scopeChain.length; i++)
842             scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace");
843         return scopeChainProxy;
844     }
845 }
846
847 InjectedScript.CallFrameProxy._scopeTypeNames = {
848     0: "global", // GLOBAL_SCOPE
849     1: "local", // LOCAL_SCOPE
850     2: "with", // WITH_SCOPE
851     3: "closure", // CLOSURE_SCOPE
852     4: "catch", // CATCH_SCOPE
853     5: "functionName", // FUNCTION_NAME_SCOPE
854 }
855
856 InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId)
857 {
858     return {
859         object: injectedScript._wrapObject(scopeObject, groupId),
860         type: InjectedScript.CallFrameProxy._scopeTypeNames[scopeTypeCode]
861     };
862 }
863
864 function BasicCommandLineAPI()
865 {
866     this.$_ = injectedScript._lastResult;
867     this.$exception = injectedScript._exceptionValue;
868 }
869
870 return injectedScript;
871 })