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