Unreviewed, rolling out r144924.
[WebKit-https.git] / Source / WebCore / inspector / InjectedScriptSource.js
1 /*
2  * Copyright (C) 2007 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 Computer, 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 /**
30  * @param {InjectedScriptHost} InjectedScriptHost
31  * @param {Window} inspectedWindow
32  * @param {number} injectedScriptId
33  */
34 (function (InjectedScriptHost, inspectedWindow, injectedScriptId) {
35
36 // Protect against Object overwritten by the user code.
37 var Object = {}.constructor;
38
39 /**
40  * @param {Arguments} array
41  * @param {number=} index
42  * @return {Array.<*>}
43  */
44 function slice(array, index)
45 {
46     var result = [];
47     for (var i = index || 0; i < array.length; ++i)
48         result.push(array[i]);
49     return result;
50 }
51
52 /**
53  * Please use this bind, not the one from Function.prototype
54  * @param {function(...)} func
55  * @param {Object} thisObject
56  * @param {...number} var_args
57  */
58 function bind(func, thisObject, var_args)
59 {
60     var args = slice(arguments, 2);
61
62     /**
63      * @param {...number} var_args
64      */
65     function bound(var_args)
66     {
67         return func.apply(thisObject, args.concat(slice(arguments)));
68     }
69     bound.toString = function() {
70         return "bound: " + func;
71     };
72     return bound;
73 }
74
75 /**
76  * @constructor
77  */
78 var InjectedScript = function()
79 {
80     this._lastBoundObjectId = 1;
81     this._idToWrappedObject = {};
82     this._idToObjectGroupName = {};
83     this._objectGroups = {};
84     this._modules = {};
85 }
86
87 /**
88  * @type {Object.<string, boolean>}
89  * @const
90  */
91 InjectedScript.primitiveTypes = {
92     undefined: true,
93     boolean: true,
94     number: true,
95     string: true
96 }
97
98 InjectedScript.prototype = {
99     /**
100      * @param {*} object
101      * @return {boolean}
102      */
103     isPrimitiveValue: function(object)
104     {
105         // FIXME(33716): typeof document.all is always 'undefined'.
106         return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
107     },
108
109     /**
110      * @param {*} object
111      * @param {string} groupName
112      * @param {boolean} canAccessInspectedWindow
113      * @param {boolean} generatePreview
114      * @return {!RuntimeAgent.RemoteObject}
115      */
116     wrapObject: function(object, groupName, canAccessInspectedWindow, generatePreview)
117     {
118         if (canAccessInspectedWindow)
119             return this._wrapObject(object, groupName, false, generatePreview);
120         return this._fallbackWrapper(object);
121     },
122
123     /**
124      * @param {*} object
125      * @return {!RuntimeAgent.RemoteObject}
126      */
127     _fallbackWrapper: function(object)
128     {
129         var result = {};
130         result.type = typeof object;
131         if (this.isPrimitiveValue(object))
132             result.value = object;
133         else
134             result.description = this._toString(object);
135         return /** @type {!RuntimeAgent.RemoteObject} */ (result);
136     },
137
138     /**
139      * @param {boolean} canAccessInspectedWindow
140      * @param {Object} table
141      * @param {Array.<string>|string|boolean} columns
142      * @return {!RuntimeAgent.RemoteObject}
143      */
144     wrapTable: function(canAccessInspectedWindow, table, columns)
145     {
146         if (!canAccessInspectedWindow)
147             return this._fallbackWrapper(table);
148         var columnNames = null;
149         if (typeof columns === "string")
150             columns = [columns];
151         if (InjectedScriptHost.type(columns) == "array") {
152             columnNames = [];
153             for (var i = 0; i < columns.length; ++i)
154                 columnNames.push(String(columns[i]));
155         }
156         return this._wrapObject(table, "console", false, true, columnNames);
157     },
158
159     /**
160      * @param {*} object
161      */
162     inspectNode: function(object)
163     {
164         this._inspect(object);
165     },
166
167     /**
168      * @param {*} object
169      * @return {*}
170      */
171     _inspect: function(object)
172     {
173         if (arguments.length === 0)
174             return;
175
176         var objectId = this._wrapObject(object, "");
177         var hints = {};
178
179         switch (injectedScript._describe(object)) {
180             case "Database":
181                 var databaseId = InjectedScriptHost.databaseId(object)
182                 if (databaseId)
183                     hints.databaseId = databaseId;
184                 break;
185             case "Storage":
186                 var storageId = InjectedScriptHost.storageId(object)
187                 if (storageId)
188                     hints.domStorageId = InjectedScriptHost.evaluate("(" + storageId + ")");
189                 break;
190         }
191         InjectedScriptHost.inspect(objectId, hints);
192         return object;
193     },
194
195     /**
196      * This method cannot throw.
197      * @param {*} object
198      * @param {string=} objectGroupName
199      * @param {boolean=} forceValueType
200      * @param {boolean=} generatePreview
201      * @param {?Array.<string>=} columnNames
202      * @return {!RuntimeAgent.RemoteObject}
203      * @suppress {checkTypes}
204      */
205     _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames)
206     {
207         try {
208             return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType, generatePreview, columnNames);
209         } catch (e) {
210             try {
211                 var description = injectedScript._describe(e);
212             } catch (ex) {
213                 var description = "<failed to convert exception to string>";
214             }
215             return new InjectedScript.RemoteObject(description);
216         }
217     },
218
219     /**
220      * @param {*} object
221      * @param {string=} objectGroupName
222      * @return {string}
223      */
224     _bind: function(object, objectGroupName)
225     {
226         var id = this._lastBoundObjectId++;
227         this._idToWrappedObject[id] = object;
228         var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}";
229         if (objectGroupName) {
230             var group = this._objectGroups[objectGroupName];
231             if (!group) {
232                 group = [];
233                 this._objectGroups[objectGroupName] = group;
234             }
235             group.push(id);
236             this._idToObjectGroupName[id] = objectGroupName;
237         }
238         return objectId;
239     },
240
241     /**
242      * @param {string} objectId
243      * @return {Object}
244      */
245     _parseObjectId: function(objectId)
246     {
247         return InjectedScriptHost.evaluate("(" + objectId + ")");
248     },
249
250     /**
251      * @param {string} objectGroupName
252      */
253     releaseObjectGroup: function(objectGroupName)
254     {
255         var group = this._objectGroups[objectGroupName];
256         if (!group)
257             return;
258         for (var i = 0; i < group.length; i++)
259             this._releaseObject(group[i]);
260         delete this._objectGroups[objectGroupName];
261     },
262
263     /**
264      * @param {string} methodName
265      * @param {string} args
266      * @return {*}
267      */
268     dispatch: function(methodName, args)
269     {
270         var argsArray = InjectedScriptHost.evaluate("(" + args + ")");
271         var result = this[methodName].apply(this, argsArray);
272         if (typeof result === "undefined") {
273             inspectedWindow.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
274             result = null;
275         }
276         return result;
277     },
278
279     /**
280      * @param {string} objectId
281      * @param {boolean} ownProperties
282      * @return {Array.<RuntimeAgent.PropertyDescriptor>|boolean}
283      */
284     getProperties: function(objectId, ownProperties)
285     {
286         var parsedObjectId = this._parseObjectId(objectId);
287         var object = this._objectForId(parsedObjectId);
288         var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
289
290         if (!this._isDefined(object))
291             return false;
292         var descriptors = this._propertyDescriptors(object, ownProperties);
293
294         // Go over properties, wrap object values.
295         for (var i = 0; i < descriptors.length; ++i) {
296             var descriptor = descriptors[i];
297             if ("get" in descriptor)
298                 descriptor.get = this._wrapObject(descriptor.get, objectGroupName);
299             if ("set" in descriptor)
300                 descriptor.set = this._wrapObject(descriptor.set, objectGroupName);
301             if ("value" in descriptor)
302                 descriptor.value = this._wrapObject(descriptor.value, objectGroupName);
303             if (!("configurable" in descriptor))
304                 descriptor.configurable = false;
305             if (!("enumerable" in descriptor))
306                 descriptor.enumerable = false;
307         }
308         return descriptors;
309     },
310
311     /**
312      * @param {string} objectId
313      * @return {Array.<Object>|boolean}
314      */
315     getInternalProperties: function(objectId, ownProperties)
316     {
317         var parsedObjectId = this._parseObjectId(objectId);
318         var object = this._objectForId(parsedObjectId);
319         var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
320         if (!this._isDefined(object))
321             return false;
322         var descriptors = [];
323         var internalProperties = InjectedScriptHost.getInternalProperties(object);
324         if (internalProperties) {
325             for (var i = 0; i < internalProperties.length; i++) {
326                 var property = internalProperties[i];
327                 var descriptor = {
328                     name: property.name,
329                     value: this._wrapObject(property.value, objectGroupName)
330                 };
331                 descriptors.push(descriptor);
332             } 
333         }
334         return descriptors;
335     },
336
337     /**
338      * @param {string} functionId
339      * @return {!DebuggerAgent.FunctionDetails|string}
340      */
341     getFunctionDetails: function(functionId)
342     {
343         var parsedFunctionId = this._parseObjectId(functionId);
344         var func = this._objectForId(parsedFunctionId);
345         if (typeof func !== "function")
346             return "Cannot resolve function by id.";
347         var details = InjectedScriptHost.functionDetails(func);
348         if ("rawScopes" in details) {
349             var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id];
350             var rawScopes = details.rawScopes;
351             var scopes = [];
352             delete details.rawScopes;
353             for (var i = 0; i < rawScopes.length; i++)
354                 scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName));
355             details.scopeChain = scopes;
356         }
357         return details;
358     },
359
360     /**
361      * @param {string} objectId
362      */
363     releaseObject: function(objectId)
364     {
365         var parsedObjectId = this._parseObjectId(objectId);
366         this._releaseObject(parsedObjectId.id);
367     },
368
369     /**
370      * @param {string} id
371      */
372     _releaseObject: function(id)
373     {
374         delete this._idToWrappedObject[id];
375         delete this._idToObjectGroupName[id];
376     },
377
378     /**
379      * @param {Object} object
380      * @param {boolean} ownProperties
381      * @return {Array.<Object>}
382      */
383     _propertyDescriptors: function(object, ownProperties)
384     {
385         var descriptors = [];
386         var nameProcessed = {};
387         nameProcessed["__proto__"] = null;
388         for (var o = object; this._isDefined(o); o = o.__proto__) {
389             var names = Object.getOwnPropertyNames(/** @type {!Object} */ (o));
390             for (var i = 0; i < names.length; ++i) {
391                 var name = names[i];
392                 if (nameProcessed[name])
393                     continue;
394
395                 try {
396                     nameProcessed[name] = true;
397                     var descriptor = Object.getOwnPropertyDescriptor(/** @type {!Object} */ (object), name);
398                     if (!descriptor) {
399                         // Not all bindings provide proper descriptors. Fall back to the writable, configurable property.
400                         try {
401                             descriptor = { name: name, value: object[name], writable: false, configurable: false, enumerable: false};
402                             if (o === object) 
403                                 descriptor.isOwn = true;
404                             descriptors.push(descriptor);
405                         } catch (e) {
406                             // Silent catch.
407                         }
408                         continue;
409                     }
410                 } catch (e) {
411                     var descriptor = {};
412                     descriptor.value = e;
413                     descriptor.wasThrown = true;
414                 }
415
416                 descriptor.name = name;
417                 if (o === object) 
418                     descriptor.isOwn = true;
419                 descriptors.push(descriptor);
420             }
421             if (ownProperties) {
422                 if (object.__proto__)
423                     descriptors.push({ name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true});
424                 break;
425             }
426         }
427         return descriptors;
428     },
429
430     /**
431      * @param {string} expression
432      * @param {string} objectGroup
433      * @param {boolean} injectCommandLineAPI
434      * @param {boolean} returnByValue
435      * @param {boolean} generatePreview
436      * @return {*}
437      */
438     evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview)
439     {
440         return this._evaluateAndWrap(InjectedScriptHost.evaluate, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview);
441     },
442
443     /**
444      * @param {string} objectId
445      * @param {string} expression
446      * @param {boolean} returnByValue
447      * @return {Object|string}
448      */
449     callFunctionOn: function(objectId, expression, args, returnByValue)
450     {
451         var parsedObjectId = this._parseObjectId(objectId);
452         var object = this._objectForId(parsedObjectId);
453         if (!this._isDefined(object))
454             return "Could not find object with given id";
455
456         if (args) {
457             var resolvedArgs = [];
458             args = InjectedScriptHost.evaluate(args);
459             for (var i = 0; i < args.length; ++i) {
460                 var resolvedCallArgument;
461                 try {
462                     resolvedCallArgument = this._resolveCallArgument(args[i]);
463                 } catch (e) {
464                     return String(e);
465                 }
466                 resolvedArgs.push(resolvedCallArgument)
467             }
468         }
469
470         try {
471             var objectGroup = this._idToObjectGroupName[parsedObjectId.id];
472             var func = InjectedScriptHost.evaluate("(" + expression + ")");
473             if (typeof func !== "function")
474                 return "Given expression does not evaluate to a function";
475
476             return { wasThrown: false,
477                      result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue) };
478         } catch (e) {
479             return this._createThrownValue(e, objectGroup);
480         }
481     },
482     
483     /**
484      * Resolves a value from CallArgument description.
485      * @param {RuntimeAgent.CallArgument} callArgumentJson
486      * @return {*} resolved value
487      * @throw {string} error message
488      */
489     _resolveCallArgument: function(callArgumentJson) {
490         var objectId = callArgumentJson.objectId;
491         if (objectId) {
492             var parsedArgId = this._parseObjectId(objectId);
493             if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId)
494                 throw "Arguments should belong to the same JavaScript world as the target object.";
495
496             var resolvedArg = this._objectForId(parsedArgId);
497             if (!this._isDefined(resolvedArg))
498                 throw "Could not find object with given id";
499
500             return resolvedArg;
501         } else if ("value" in callArgumentJson)
502             return callArgumentJson.value;
503         else
504             return undefined;
505     },
506
507     /**
508      * @param {Function} evalFunction
509      * @param {Object} object
510      * @param {string} objectGroup
511      * @param {boolean} isEvalOnCallFrame
512      * @param {boolean} injectCommandLineAPI
513      * @param {boolean} returnByValue
514      * @param {boolean} generatePreview
515      * @return {*}
516      */
517     _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview)
518     {
519         try {
520             return { wasThrown: false,
521                      result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI), objectGroup, returnByValue, generatePreview) };
522         } catch (e) {
523             return this._createThrownValue(e, objectGroup);
524         }
525     },
526
527     /**
528      * @param {*} value
529      * @param {string} objectGroup
530      * @return {Object}
531      */
532     _createThrownValue: function(value, objectGroup)
533     {
534         var remoteObject = this._wrapObject(value, objectGroup);
535         try {
536             remoteObject.description = this._toString(value);
537         } catch (e) {}
538         return { wasThrown: true,
539                  result: remoteObject };
540     },
541
542     /**
543      * @param {Function} evalFunction
544      * @param {Object} object
545      * @param {string} objectGroup
546      * @param {string} expression
547      * @param {boolean} isEvalOnCallFrame
548      * @param {boolean} injectCommandLineAPI
549      * @return {*}
550      */
551     _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI)
552     {
553         // Only install command line api object for the time of evaluation.
554         // Surround the expression in with statements to inject our command line API so that
555         // the window object properties still take more precedent than our API functions.
556
557         try {
558             if (injectCommandLineAPI && inspectedWindow.console) {
559                 inspectedWindow.console._commandLineAPI = new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
560                 expression = "with ((window && window.console && window.console._commandLineAPI) || {}) {\n" + expression + "\n}";
561             }
562             var result = evalFunction.call(object, expression);
563             if (objectGroup === "console")
564                 this._lastResult = result;
565             return result;
566         } finally {
567             if (injectCommandLineAPI && inspectedWindow.console)
568                 delete inspectedWindow.console._commandLineAPI;
569         }
570     },
571
572     /**
573      * @param {Object} callFrame
574      * @return {Array.<InjectedScript.CallFrameProxy>|boolean}
575      */
576     wrapCallFrames: function(callFrame)
577     {
578         if (!callFrame)
579             return false;
580
581         var result = [];
582         var depth = 0;
583         do {
584             result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
585             callFrame = callFrame.caller;
586         } while (callFrame);
587         return result;
588     },
589
590     /**
591      * @param {Object} topCallFrame
592      * @param {string} callFrameId
593      * @param {string} expression
594      * @param {string} objectGroup
595      * @param {boolean} injectCommandLineAPI
596      * @param {boolean} returnByValue
597      * @param {boolean} generatePreview
598      * @return {*}
599      */
600     evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview)
601     {
602         var callFrame = this._callFrameForId(topCallFrame, callFrameId);
603         if (!callFrame)
604             return "Could not find call frame with given id";
605         return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview);
606     },
607
608     /**
609      * @param {Object} topCallFrame
610      * @param {string} callFrameId
611      * @return {*}
612      */
613     restartFrame: function(topCallFrame, callFrameId)
614     {
615         var callFrame = this._callFrameForId(topCallFrame, callFrameId);
616         if (!callFrame)
617             return "Could not find call frame with given id";
618         var result = callFrame.restart();
619         if (result === false)
620             result = "Restart frame is not supported"; 
621         return result;
622     },
623
624     /**
625      * Either callFrameId or functionObjectId must be specified.
626      * @param {Object} topCallFrame
627      * @param {string|boolean} callFrameId or false
628      * @param {string|boolean} functionObjectId or false
629      * @param {number} scopeNumber
630      * @param {string} variableName
631      * @param {string} newValueJsonString RuntimeAgent.CallArgument structure serialized as string 
632      * @return {string|undefined} undefined if success or an error message 
633      */
634     setVariableValue: function(topCallFrame, callFrameId, functionObjectId, scopeNumber, variableName, newValueJsonString)
635     {   
636         var setter;
637         if (typeof callFrameId === "string") {
638             var callFrame = this._callFrameForId(topCallFrame, callFrameId);
639             if (!callFrame)
640                 return "Could not find call frame with given id";
641             setter = callFrame.setVariableValue.bind(callFrame);    
642         } else {
643             var parsedFunctionId = this._parseObjectId(/** @type {string} */(functionObjectId));
644             var func = this._objectForId(parsedFunctionId);
645             if (typeof func !== "function")
646                 return "Cannot resolve function by id.";
647             setter = InjectedScriptHost.setFunctionVariableValue.bind(InjectedScriptHost, func); 
648         }
649         var newValueJson;
650         try {
651             newValueJson = InjectedScriptHost.evaluate("(" + newValueJsonString + ")");
652         } catch (e) {
653             return "Failed to parse new value JSON " + newValueJsonString + " : " + e;
654         }
655         var resolvedValue;
656         try {
657             resolvedValue = this._resolveCallArgument(newValueJson);
658         } catch (e) {
659             return String(e);
660         }
661         try {
662             setter(scopeNumber, variableName, resolvedValue);
663         } catch (e) {
664             return "Failed to change variable value: " + e;
665         }
666         return undefined;
667     },
668
669     /**
670      * @param {Object} topCallFrame
671      * @param {string} callFrameId
672      * @return {Object}
673      */
674     _callFrameForId: function(topCallFrame, callFrameId)
675     {
676         var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")");
677         var ordinal = parsedCallFrameId["ordinal"];
678         var callFrame = topCallFrame;
679         while (--ordinal >= 0 && callFrame)
680             callFrame = callFrame.caller;
681         return callFrame;
682     },
683
684     /**
685      * @param {Object} objectId
686      * @return {Object}
687      */
688     _objectForId: function(objectId)
689     {
690         return this._idToWrappedObject[objectId.id];
691     },
692
693     /**
694      * @param {string} objectId
695      * @return {Object}
696      */
697     findObjectById: function(objectId)
698     {
699         var parsedObjectId = this._parseObjectId(objectId);
700         return this._objectForId(parsedObjectId);
701     },
702
703     /**
704      * @param {string} objectId
705      * @return {Node}
706      */
707     nodeForObjectId: function(objectId)
708     {
709         var object = this.findObjectById(objectId);
710         if (!object || this._subtype(object) !== "node")
711             return null;
712         return /** @type {Node} */ (object);
713     },
714
715     /**
716      * @param {string} name
717      * @return {Object}
718      */ 
719     module: function(name)
720     {
721         return this._modules[name];
722     },
723
724     /**
725      * @param {string} name
726      * @param {string} source
727      * @return {Object}
728      */ 
729     injectModule: function(name, source)
730     {
731         delete this._modules[name];
732         var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")");
733         if (typeof moduleFunction !== "function") {
734             inspectedWindow.console.error("Web Inspector error: A function was expected for module %s evaluation", name);
735             return null;
736         }
737         var module = moduleFunction.call(inspectedWindow, InjectedScriptHost, inspectedWindow, injectedScriptId);
738         this._modules[name] = module;
739         return module;
740     },
741
742     /**
743      * @param {*} object
744      * @return {boolean}
745      */
746     _isDefined: function(object)
747     {
748         return !!object || this._isHTMLAllCollection(object);
749     },
750
751     /**
752      * @param {*} object
753      * @return {boolean}
754      */
755     _isHTMLAllCollection: function(object)
756     {
757         // document.all is reported as undefined, but we still want to process it.
758         return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object);
759     },
760
761     /**
762      * @param {Object=} obj
763      * @return {string?}
764      */
765     _subtype: function(obj)
766     {
767         if (obj === null)
768             return "null";
769
770         if (this.isPrimitiveValue(obj))
771             return null;
772
773         if (this._isHTMLAllCollection(obj))
774             return "array";
775
776         var preciseType = InjectedScriptHost.type(obj);
777         if (preciseType)
778             return preciseType;
779
780         // FireBug's array detection.
781         try {
782             if (typeof obj.splice === "function" && isFinite(obj.length))
783                 return "array";
784             if (Object.prototype.toString.call(obj) === "[object Arguments]" && isFinite(obj.length)) // arguments.
785                 return "array";
786         } catch (e) {
787         }
788
789         // If owning frame has navigated to somewhere else window properties will be undefined.
790         return null;
791     },
792
793     /**
794      * @param {*} obj
795      * @return {string?}
796      */
797     _describe: function(obj)
798     {
799         if (this.isPrimitiveValue(obj))
800             return null;
801
802         obj = /** @type {Object} */ (obj);
803
804         // Type is object, get subtype.
805         var subtype = this._subtype(obj);
806
807         if (subtype === "regexp")
808             return this._toString(obj);
809
810         if (subtype === "date")
811             return this._toString(obj);
812
813         if (subtype === "node") {
814             var description = obj.nodeName.toLowerCase();
815             switch (obj.nodeType) {
816             case 1 /* Node.ELEMENT_NODE */:
817                 description += obj.id ? "#" + obj.id : "";
818                 var className = obj.className;
819                 description += className ? "." + className : "";
820                 break;
821             case 10 /*Node.DOCUMENT_TYPE_NODE */:
822                 description = "<!DOCTYPE " + description + ">";
823                 break;
824             }
825             return description;
826         }
827
828         var className = InjectedScriptHost.internalConstructorName(obj);
829         if (subtype === "array") {
830             if (typeof obj.length === "number")
831                 className += "[" + obj.length + "]";
832             return className;
833         }
834
835         // NodeList in JSC is a function, check for array prior to this.
836         if (typeof obj === "function")
837             return this._toString(obj);
838
839         if (className === "Object") {
840             // In Chromium DOM wrapper prototypes will have Object as their constructor name,
841             // get the real DOM wrapper name from the constructor property.
842             var constructorName = obj.constructor && obj.constructor.name;
843             if (constructorName)
844                 return constructorName;
845         }
846         return className;
847     },
848
849     /**
850      * @param {*} obj
851      * @return {string}
852      */
853     _toString: function(obj)
854     {
855         // We don't use String(obj) because inspectedWindow.String is undefined if owning frame navigated to another page.
856         return "" + obj;
857     }
858 }
859
860 /**
861  * @type {InjectedScript}
862  * @const
863  */
864 var injectedScript = new InjectedScript();
865
866 /**
867  * @constructor
868  * @param {*} object
869  * @param {string=} objectGroupName
870  * @param {boolean=} forceValueType
871  * @param {boolean=} generatePreview
872  * @param {?Array.<string>=} columnNames
873  */
874 InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames)
875 {
876     this.type = typeof object;
877     if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
878         // We don't send undefined values over JSON.
879         if (typeof object !== "undefined")
880             this.value = object;
881
882         // Null object is object with 'null' subtype'
883         if (object === null)
884             this.subtype = "null";
885
886         // Provide user-friendly number values.
887         if (typeof object === "number")
888             this.description = object + "";
889         return;
890     }
891
892     object = /** @type {Object} */ (object);
893
894     this.objectId = injectedScript._bind(object, objectGroupName);
895     var subtype = injectedScript._subtype(object);
896     if (subtype)
897         this.subtype = subtype;
898     this.className = InjectedScriptHost.internalConstructorName(object);
899     this.description = injectedScript._describe(object);
900
901     if (generatePreview && (this.type === "object" || injectedScript._isHTMLAllCollection(object)))
902         this.preview = this._generatePreview(object, undefined, columnNames);
903 }
904
905 InjectedScript.RemoteObject.prototype = {
906     /**
907      * @param {Object} object
908      * @param {Array.<string>=} firstLevelKeys
909      * @param {?Array.<string>=} secondLevelKeys
910      * @return {Object} preview
911      */
912     _generatePreview: function(object, firstLevelKeys, secondLevelKeys)
913     {
914         var preview = {};
915         preview.lossless = true;
916         preview.overflow = false;
917         preview.properties = [];
918
919         var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys;
920         var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
921
922         var propertiesThreshold = {
923             properties: isTableRowsRequest ? 1000 : Math.max(5, firstLevelKeysCount),
924             indexes: isTableRowsRequest ? 1000 : Math.max(100, firstLevelKeysCount)
925         };
926         for (var o = object; injectedScript._isDefined(o); o = o.__proto__)
927             this._generateProtoPreview(o, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys);
928         return preview;
929     },
930
931     /**
932      * @param {Object} object
933      * @param {Object} preview
934      * @param {Object} propertiesThreshold
935      * @param {Array.<string>=} firstLevelKeys
936      * @param {Array.<string>=} secondLevelKeys
937      */
938     _generateProtoPreview: function(object, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys)
939     {
940         var propertyNames = firstLevelKeys ? firstLevelKeys : Object.keys(/** @type {!Object} */(object));
941         try {
942             for (var i = 0; i < propertyNames.length; ++i) {
943                 if (!propertiesThreshold.properties || !propertiesThreshold.indexes) {
944                     preview.overflow = true;
945                     preview.lossless = false;
946                     break;
947                 }
948                 var name = propertyNames[i];
949                 if (this.subtype === "array" && name === "length")
950                     continue;
951
952                 var descriptor = Object.getOwnPropertyDescriptor(/** @type {!Object} */(object), name);
953                 if (!("value" in descriptor) || !descriptor.enumerable) {
954                     preview.lossless = false;
955                     continue;
956                 }
957
958                 var value = descriptor.value;
959                 if (value === null) {
960                     this._appendPropertyPreview(preview, { name: name, type: "object", value: "null" }, propertiesThreshold);
961                     continue;
962                 }
963     
964                 const maxLength = 100;
965                 var type = typeof value;
966
967                 if (InjectedScript.primitiveTypes[type]) {
968                     if (type === "string") {
969                         if (value.length > maxLength) {
970                             value = this._abbreviateString(value, maxLength, true);
971                             preview.lossless = false;
972                         }
973                         value = value.replace(/\n/g, "\u21B5");
974                     }
975                     this._appendPropertyPreview(preview, { name: name, type: type, value: value + "" }, propertiesThreshold);
976                     continue;
977                 }
978
979                 if (secondLevelKeys === null || secondLevelKeys) {
980                     var subPreview = this._generatePreview(value, secondLevelKeys || undefined);
981                     var property = { name: name, type: type, valuePreview: subPreview };
982                     this._appendPropertyPreview(preview, property, propertiesThreshold);
983                     if (!subPreview.lossless)
984                         preview.lossless = false;
985                     if (subPreview.overflow)
986                         preview.overflow = true;
987                     continue;
988                 }
989
990                 preview.lossless = false;
991
992                 var subtype = injectedScript._subtype(value);
993                 var description = "";
994                 if (type !== "function")
995                     description = this._abbreviateString(/** @type {string} */ (injectedScript._describe(value)), maxLength, subtype === "regexp");
996
997                 var property = { name: name, type: type, value: description };
998                 if (subtype)
999                     property.subtype = subtype;
1000                 this._appendPropertyPreview(preview, property, propertiesThreshold);
1001             }
1002         } catch (e) {
1003         }
1004     },
1005
1006     /**
1007      * @param {Object} preview
1008      * @param {Object} property
1009      * @param {Object} propertiesThreshold
1010      */
1011     _appendPropertyPreview: function(preview, property, propertiesThreshold)
1012     {
1013         if (isNaN(property.name))
1014             propertiesThreshold.properties--;
1015         else
1016             propertiesThreshold.indexes--;
1017         preview.properties.push(property);
1018     },
1019
1020     /**
1021      * @param {string} string
1022      * @param {number} maxLength
1023      * @param {boolean=} middle
1024      * @returns
1025      */
1026     _abbreviateString: function(string, maxLength, middle)
1027     {
1028         if (string.length <= maxLength)
1029             return string;
1030         if (middle) {
1031             var leftHalf = maxLength >> 1;
1032             var rightHalf = maxLength - leftHalf - 1;
1033             return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf);
1034         }
1035         return string.substr(0, maxLength) + "\u2026";
1036     }
1037 }
1038 /**
1039  * @constructor
1040  * @param {number} ordinal
1041  * @param {Object} callFrame
1042  */
1043 InjectedScript.CallFrameProxy = function(ordinal, callFrame)
1044 {
1045     this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}";
1046     this.functionName = (callFrame.type === "function" ? callFrame.functionName : "");
1047     this.location = { scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column };
1048     this.scopeChain = this._wrapScopeChain(callFrame);
1049     this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace");
1050 }
1051
1052 InjectedScript.CallFrameProxy.prototype = {
1053     /**
1054      * @param {Object} callFrame
1055      * @return {!Array.<DebuggerAgent.Scope>}
1056      */
1057     _wrapScopeChain: function(callFrame)
1058     {
1059         var scopeChain = callFrame.scopeChain;
1060         var scopeChainProxy = [];
1061         for (var i = 0; i < scopeChain.length; i++) {
1062             var scope = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace");
1063             scopeChainProxy.push(scope);
1064         }
1065         return scopeChainProxy;
1066     }
1067 }
1068
1069 /**
1070  * @param {number} scopeTypeCode
1071  * @param {*} scopeObject
1072  * @param {string} groupId
1073  * @return {!DebuggerAgent.Scope}
1074  */
1075 InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId) {
1076     const GLOBAL_SCOPE = 0;
1077     const LOCAL_SCOPE = 1;
1078     const WITH_SCOPE = 2;
1079     const CLOSURE_SCOPE = 3;
1080     const CATCH_SCOPE = 4;
1081
1082     /** @type {!Object.<number, string>} */
1083     var scopeTypeNames = {};
1084     scopeTypeNames[GLOBAL_SCOPE] = "global";
1085     scopeTypeNames[LOCAL_SCOPE] = "local";
1086     scopeTypeNames[WITH_SCOPE] = "with";
1087     scopeTypeNames[CLOSURE_SCOPE] = "closure";
1088     scopeTypeNames[CATCH_SCOPE] = "catch";
1089
1090     return {
1091         object: injectedScript._wrapObject(scopeObject, groupId),
1092         type: /** @type {DebuggerAgent.ScopeType} */ (scopeTypeNames[scopeTypeCode])
1093     };
1094 }
1095
1096 /**
1097  * @constructor
1098  * @param {CommandLineAPIImpl} commandLineAPIImpl
1099  * @param {Object} callFrame
1100  */
1101 function CommandLineAPI(commandLineAPIImpl, callFrame)
1102 {
1103     /**
1104      * @param {string} member
1105      * @return {boolean}
1106      */
1107     function inScopeVariables(member)
1108     {
1109         if (!callFrame)
1110             return false;
1111
1112         var scopeChain = callFrame.scopeChain;
1113         for (var i = 0; i < scopeChain.length; ++i) {
1114             if (member in scopeChain[i])
1115                 return true;
1116         }
1117         return false;
1118     }
1119
1120     /**
1121      * @param {string} name The name of the method for which a toString method should be generated.
1122      * @return {function():string}
1123      */
1124     function customToStringMethod(name)
1125     {
1126         return function () { return "function " + name + "() { [Command Line API] }"; };
1127     }
1128
1129     for (var i = 0; i < CommandLineAPI.members_.length; ++i) {
1130         var member = CommandLineAPI.members_[i];
1131         if (member in inspectedWindow || inScopeVariables(member))
1132             continue;
1133
1134         this[member] = bind(commandLineAPIImpl[member], commandLineAPIImpl);
1135         this[member].toString = customToStringMethod(member);
1136     }
1137
1138     for (var i = 0; i < 5; ++i) {
1139         var member = "$" + i;
1140         if (member in inspectedWindow || inScopeVariables(member))
1141             continue;
1142
1143         this.__defineGetter__("$" + i, bind(commandLineAPIImpl._inspectedObject, commandLineAPIImpl, i));
1144     }
1145
1146     this.$_ = injectedScript._lastResult;
1147 }
1148
1149 // NOTE: Please keep the list of API methods below snchronized to that in WebInspector.RuntimeModel!
1150 /**
1151  * @type {Array.<string>}
1152  * @const
1153  */
1154 CommandLineAPI.members_ = [
1155     "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd",
1156     "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners"
1157 ];
1158
1159 /**
1160  * @constructor
1161  */
1162 function CommandLineAPIImpl()
1163 {
1164 }
1165
1166 CommandLineAPIImpl.prototype = {
1167     /**
1168      * @param {string} selector
1169      * @param {Node=} start
1170      */
1171     $: function (selector, start)
1172     {
1173         if (this._canQuerySelectorOnNode(start))
1174             return start.querySelector(selector);
1175
1176         var result = inspectedWindow.document.querySelector(selector);
1177         if (result)
1178             return result;
1179         if (selector && selector[0] !== "#") {
1180             result = inspectedWindow.document.getElementById(selector);
1181             if (result) {
1182                 inspectedWindow.console.warn("The console function $() has changed from $=getElementById(id) to $=querySelector(selector). You might try $(\"#%s\")", selector );
1183                 return null;
1184             }
1185         }
1186         return result;
1187     },
1188
1189     /**
1190      * @param {string} selector
1191      * @param {Node=} start
1192      */
1193     $$: function (selector, start)
1194     {
1195         if (this._canQuerySelectorOnNode(start))
1196             return start.querySelectorAll(selector);
1197         return inspectedWindow.document.querySelectorAll(selector);
1198     },
1199
1200     /**
1201      * @param {Node=} node
1202      * @return {boolean}
1203      */
1204     _canQuerySelectorOnNode: function(node)
1205     {
1206         return !!node && InjectedScriptHost.type(node) === "node" && (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE);
1207     },
1208
1209     /**
1210      * @param {string} xpath
1211      * @param {Node=} context
1212      */
1213     $x: function(xpath, context)
1214     {
1215         var doc = (context && context.ownerDocument) || inspectedWindow.document;
1216         var result = doc.evaluate(xpath, context || doc, null, XPathResult.ANY_TYPE, null);
1217         switch (result.resultType) {
1218         case XPathResult.NUMBER_TYPE:
1219             return result.numberValue;
1220         case XPathResult.STRING_TYPE:
1221             return result.stringValue;
1222         case XPathResult.BOOLEAN_TYPE:
1223             return result.booleanValue;
1224         default:
1225             var nodes = [];
1226             var node;
1227             while (node = result.iterateNext())
1228                 nodes.push(node);
1229             return nodes;
1230         }
1231     },
1232
1233     dir: function()
1234     {
1235         return inspectedWindow.console.dir.apply(inspectedWindow.console, arguments)
1236     },
1237
1238     dirxml: function()
1239     {
1240         return inspectedWindow.console.dirxml.apply(inspectedWindow.console, arguments)
1241     },
1242
1243     keys: function(object)
1244     {
1245         return Object.keys(object);
1246     },
1247
1248     values: function(object)
1249     {
1250         var result = [];
1251         for (var key in object)
1252             result.push(object[key]);
1253         return result;
1254     },
1255
1256     profile: function()
1257     {
1258         return inspectedWindow.console.profile.apply(inspectedWindow.console, arguments)
1259     },
1260
1261     profileEnd: function()
1262     {
1263         return inspectedWindow.console.profileEnd.apply(inspectedWindow.console, arguments)
1264     },
1265
1266     /**
1267      * @param {Object} object
1268      * @param {Array.<string>|string=} types
1269      */
1270     monitorEvents: function(object, types)
1271     {
1272         if (!object || !object.addEventListener || !object.removeEventListener)
1273             return;
1274         types = this._normalizeEventTypes(types);
1275         for (var i = 0; i < types.length; ++i) {
1276             object.removeEventListener(types[i], this._logEvent, false);
1277             object.addEventListener(types[i], this._logEvent, false);
1278         }
1279     },
1280
1281     /**
1282      * @param {Object} object
1283      * @param {Array.<string>|string=} types
1284      */
1285     unmonitorEvents: function(object, types)
1286     {
1287         if (!object || !object.addEventListener || !object.removeEventListener)
1288             return;
1289         types = this._normalizeEventTypes(types);
1290         for (var i = 0; i < types.length; ++i)
1291             object.removeEventListener(types[i], this._logEvent, false);
1292     },
1293
1294     /**
1295      * @param {*} object
1296      * @return {*}
1297      */
1298     inspect: function(object)
1299     {
1300         return injectedScript._inspect(object);
1301     },
1302
1303     copy: function(object)
1304     {
1305         if (injectedScript._subtype(object) === "node")
1306             object = object.outerHTML;
1307         InjectedScriptHost.copyText(object);
1308     },
1309
1310     clear: function()
1311     {
1312         InjectedScriptHost.clearConsoleMessages();
1313     },
1314
1315     /**
1316      * @param {Node} node
1317      */
1318     getEventListeners: function(node)
1319     {
1320         return InjectedScriptHost.getEventListeners(node);
1321     },
1322
1323     /**
1324      * @param {number} num
1325      */
1326     _inspectedObject: function(num)
1327     {
1328         return InjectedScriptHost.inspectedObject(num);
1329     },
1330
1331     /**
1332      * @param {Array.<string>|string=} types
1333      * @return {Array.<string>}
1334      */
1335     _normalizeEventTypes: function(types)
1336     {
1337         if (typeof types === "undefined")
1338             types = [ "mouse", "key", "touch", "control", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll", "search", "devicemotion", "deviceorientation" ];
1339         else if (typeof types === "string")
1340             types = [ types ];
1341
1342         var result = [];
1343         for (var i = 0; i < types.length; i++) {
1344             if (types[i] === "mouse")
1345                 result.splice(0, 0, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout", "mousewheel");
1346             else if (types[i] === "key")
1347                 result.splice(0, 0, "keydown", "keyup", "keypress", "textInput");
1348             else if (types[i] === "touch")
1349                 result.splice(0, 0, "touchstart", "touchmove", "touchend", "touchcancel");
1350             else if (types[i] === "control")
1351                 result.splice(0, 0, "resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset");
1352             else
1353                 result.push(types[i]);
1354         }
1355         return result;
1356     },
1357
1358     /**
1359      * @param {Event} event
1360      */
1361     _logEvent: function(event)
1362     {
1363         inspectedWindow.console.log(event.type, event);
1364     }
1365 }
1366
1367 injectedScript._commandLineAPIImpl = new CommandLineAPIImpl();
1368 return injectedScript;
1369 })