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