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