0335c986864bf868845fe8ac4e9ca937d7bae422
[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 /**
37  * @constructor
38  */
39 var InjectedScript = function()
40 {
41     this._lastBoundObjectId = 1;
42     this._idToWrappedObject = {};
43     this._idToObjectGroupName = {};
44     this._objectGroups = {};
45     this._modules = {};
46 }
47
48 /**
49  * @type {Object.<string, boolean>}
50  * @const
51  */
52 InjectedScript.primitiveTypes = {
53     undefined: true,
54     boolean: true,
55     number: true,
56     string: true
57 }
58
59 InjectedScript.prototype = {
60     /**
61      * @param {*} object
62      * @return {boolean}
63      */
64     isPrimitiveValue: function(object)
65     {
66         // FIXME(33716): typeof document.all is always 'undefined'.
67         return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
68     },
69
70     /**
71      * @param {*} object
72      * @param {string} groupName
73      * @param {boolean} canAccessInspectedWindow
74      * @return {Object}
75      */
76     wrapObject: function(object, groupName, canAccessInspectedWindow)
77     {
78         if (canAccessInspectedWindow)
79             return this._wrapObject(object, groupName);
80
81         var result = {};
82         result.type = typeof object;
83         if (this.isPrimitiveValue(object))
84             result.value = object;
85         else
86             result.description = this._toString(object);
87         return result;
88     },
89
90     /**
91      * @param {*} object
92      */
93     inspectNode: function(object)
94     {
95         this._inspect(object);
96     },
97
98     /**
99      * @param {*} object
100      * @return {*}
101      */
102     _inspect: function(object)
103     {
104         if (arguments.length === 0)
105             return;
106
107         var objectId = this._wrapObject(object, "");
108         var hints = {};
109
110         switch (injectedScript._describe(object)) {
111             case "Database":
112                 var databaseId = InjectedScriptHost.databaseId(object)
113                 if (databaseId)
114                     hints.databaseId = databaseId;
115                 break;
116             case "Storage":
117                 var storageId = InjectedScriptHost.storageId(object)
118                 if (storageId)
119                     hints.domStorageId = storageId;
120                 break;
121         }
122         InjectedScriptHost.inspect(objectId, hints);
123         return object;
124     },
125
126     /**
127      * This method cannot throw.
128      * @param {*} object
129      * @param {string=} objectGroupName
130      * @param {boolean=} forceValueType
131      * @return {InjectedScript.RemoteObject}
132      */
133     _wrapObject: function(object, objectGroupName, forceValueType)
134     {
135         try {
136             return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType);
137         } catch (e) {
138             try {
139                 var description = injectedScript._describe(e);
140             } catch (ex) {
141                 var description = "<failed to convert exception to string>";
142             }
143             return new InjectedScript.RemoteObject(description);
144         }
145     },
146
147     /**
148      * @param {*} object
149      * @param {string=} objectGroupName
150      * @return {string}
151      */
152     _bind: function(object, objectGroupName)
153     {
154         var id = this._lastBoundObjectId++;
155         this._idToWrappedObject[id] = object;
156         var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}";
157         if (objectGroupName) {
158             var group = this._objectGroups[objectGroupName];
159             if (!group) {
160                 group = [];
161                 this._objectGroups[objectGroupName] = group;
162             }
163             group.push(id);
164             this._idToObjectGroupName[id] = objectGroupName;
165         }
166         return objectId;
167     },
168
169     /**
170      * @param {string} objectId
171      * @return {*}
172      */
173     _parseObjectId: function(objectId)
174     {
175         return eval("(" + objectId + ")");
176     },
177
178     /**
179      * @param {string} objectGroupName
180      */
181     releaseObjectGroup: function(objectGroupName)
182     {
183         var group = this._objectGroups[objectGroupName];
184         if (!group)
185             return;
186         for (var i = 0; i < group.length; i++)
187             this._releaseObject(group[i]);
188         delete this._objectGroups[objectGroupName];
189     },
190
191     /**
192      * @param {string} methodName
193      * @param {string} args
194      * @return {*}
195      */
196     dispatch: function(methodName, args)
197     {
198         var argsArray = eval("(" + args + ")");
199         var result = this[methodName].apply(this, argsArray);
200         if (typeof result === "undefined") {
201             inspectedWindow.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
202             result = null;
203         }
204         return result;
205     },
206
207     /**
208      * @param {string} objectId
209      * @param {boolean} ownProperties
210      * @return {Array.<Object>|boolean}
211      */
212     getProperties: function(objectId, ownProperties)
213     {
214         var parsedObjectId = this._parseObjectId(objectId);
215         var object = this._objectForId(parsedObjectId);
216         var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
217
218         if (!this._isDefined(object))
219             return false;
220         var descriptors = this._propertyDescriptors(object, ownProperties);
221
222         // Go over properties, wrap object values.
223         if (descriptors.length === 0 && "arguments" in object) {
224             // Fill in JSC scope object.
225             for (var key in object)
226                 descriptors.push({ name: key, value: object[key], writable: false, configurable: false, enumerable: true});
227         }
228
229         for (var i = 0; i < descriptors.length; ++i) {
230             var descriptor = descriptors[i];
231             if ("get" in descriptor)
232                 descriptor.get = this._wrapObject(descriptor.get, objectGroupName);
233             if ("set" in descriptor)
234                 descriptor.set = this._wrapObject(descriptor.set, objectGroupName);
235             if ("value" in descriptor)
236                 descriptor.value = this._wrapObject(descriptor.value, objectGroupName);
237             if (!("configurable" in descriptor))
238                 descriptor.configurable = false;
239             if (!("enumerable" in descriptor))
240                 descriptor.enumerable = false;
241             
242         }
243         return descriptors;
244     },
245
246     /**
247      * @param {string} functionId
248      * @return {Object|string}
249      */
250     getFunctionDetails: function(functionId)
251     {
252         var parsedFunctionId = this._parseObjectId(functionId);
253         var func = this._objectForId(parsedFunctionId);
254         if (typeof func !== "function")
255             return "Cannot resolve function by id.";
256         var details = InjectedScriptHost.functionDetails(func);
257         if ("rawScopes" in details) {
258             var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id];
259             var rawScopes = details.rawScopes;
260             var scopes = [];
261             delete details.rawScopes;
262             for (var i = 0; i < rawScopes.length; i++)
263                 scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName));
264             details.scopeChain = scopes;
265         }
266         return details;
267     },
268
269     /**
270      * @param {string} objectId
271      */
272     releaseObject: function(objectId)
273     {
274         var parsedObjectId = this._parseObjectId(objectId);
275         this._releaseObject(parsedObjectId.id);
276     },
277
278     /**
279      * @param {string} id
280      */
281     _releaseObject: function(id)
282     {
283         delete this._idToWrappedObject[id];
284         delete this._idToObjectGroupName[id];
285     },
286
287     /**
288      * @param {Object} object
289      * @param {boolean} ownProperties
290      * @return {Array.<Object>}
291      */
292     _propertyDescriptors: function(object, ownProperties)
293     {
294         var descriptors = [];
295         var nameProcessed = {};
296         nameProcessed["__proto__"] = null;
297         for (var o = object; this._isDefined(o); o = o.__proto__) {
298             var names = Object.getOwnPropertyNames(/** @type {!Object} */ (o));
299             for (var i = 0; i < names.length; ++i) {
300                 var name = names[i];
301                 if (nameProcessed[name])
302                     continue;
303
304                 try {
305                     nameProcessed[name] = true;
306                     var descriptor = Object.getOwnPropertyDescriptor(/** @type {!Object} */ (object), name);
307                     if (!descriptor) {
308                         // Not all bindings provide proper descriptors. Fall back to the writable, configurable property.
309                         try {
310                             descriptors.push({ name: name, value: object[name], writable: false, configurable: false, enumerable: false});
311                         } catch (e) {
312                             // Silent catch.
313                         }
314                         continue;
315                     }
316                 } catch (e) {
317                     var descriptor = {};
318                     descriptor.value = e;
319                     descriptor.wasThrown = true;
320                 }
321
322                 descriptor.name = name;
323                 descriptors.push(descriptor); 
324             }
325             if (ownProperties) {
326                 if (object.__proto__)
327                     descriptors.push({ name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false});
328                 break;
329             }
330         }
331         return descriptors;
332     },
333
334     /**
335      * @param {string} expression
336      * @param {string} objectGroup
337      * @param {boolean} injectCommandLineAPI
338      * @param {boolean} returnByValue
339      * @return {*}
340      */
341     evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue)
342     {
343         return this._evaluateAndWrap(inspectedWindow.eval, inspectedWindow, expression, objectGroup, false, injectCommandLineAPI, returnByValue);
344     },
345
346     /**
347      * @param {string} objectId
348      * @param {string} expression
349      * @param {boolean} returnByValue
350      * @return {Object|string}
351      */
352     callFunctionOn: function(objectId, expression, args, returnByValue)
353     {
354         var parsedObjectId = this._parseObjectId(objectId);
355         var object = this._objectForId(parsedObjectId);
356         if (!this._isDefined(object))
357             return "Could not find object with given id";
358
359         if (args) {
360             var resolvedArgs = [];
361             args = eval(args);
362             for (var i = 0; i < args.length; ++i) {
363                 objectId = args[i].objectId;
364                 if (objectId) {
365                     var parsedArgId = this._parseObjectId(objectId);
366                     if (!parsedArgId || parsedArgId.injectedScriptId !== injectedScriptId)
367                         return "Arguments should belong to the same JavaScript world as the target object.";
368
369                     var resolvedArg = this._objectForId(parsedArgId);
370                     if (!this._isDefined(resolvedArg))
371                         return "Could not find object with given id";
372
373                     resolvedArgs.push(resolvedArg);
374                 } else if ("value" in args[i])
375                     resolvedArgs.push(args[i].value);
376                 else
377                     resolvedArgs.push(undefined);
378             }
379         }
380
381         try {
382             var objectGroup = this._idToObjectGroupName[parsedObjectId.id];
383             var func = eval("(" + expression + ")");
384             if (typeof func !== "function")
385                 return "Given expression does not evaluate to a function";
386
387             return { wasThrown: false,
388                      result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue) };
389         } catch (e) {
390             return this._createThrownValue(e, objectGroup);
391         }
392     },
393
394     /**
395      * @param {Function} evalFunction
396      * @param {Object} object
397      * @param {string} objectGroup
398      * @param {boolean} isEvalOnCallFrame
399      * @param {boolean} injectCommandLineAPI
400      * @param {boolean} returnByValue
401      * @return {*}
402      */
403     _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue)
404     {
405         try {
406             return { wasThrown: false,
407                      result: this._wrapObject(this._evaluateOn(evalFunction, object, expression, isEvalOnCallFrame, injectCommandLineAPI), objectGroup, returnByValue) };
408         } catch (e) {
409             return this._createThrownValue(e, objectGroup);
410         }
411     },
412
413     /**
414      * @param {*} value
415      * @param {string} objectGroup
416      * @return {Object}
417      */
418     _createThrownValue: function(value, objectGroup)
419     {
420         var remoteObject = this._wrapObject(value, objectGroup);
421         try {
422             remoteObject.description = this._toString(value);
423         } catch (e) {}
424         return { wasThrown: true,
425                  result: remoteObject };
426     },
427
428     /**
429      * @param {Function} evalFunction
430      * @param {Object} object
431      * @param {string} expression
432      * @param {boolean} isEvalOnCallFrame
433      * @param {boolean} injectCommandLineAPI
434      * @return {*}
435      */
436     _evaluateOn: function(evalFunction, object, expression, isEvalOnCallFrame, injectCommandLineAPI)
437     {
438         // Only install command line api object for the time of evaluation.
439         // Surround the expression in with statements to inject our command line API so that
440         // the window object properties still take more precedent than our API functions.
441
442         try {
443             if (injectCommandLineAPI && inspectedWindow.console) {
444                 inspectedWindow.console._commandLineAPI = new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
445                 expression = "with ((window && window.console && window.console._commandLineAPI) || {}) {\n" + expression + "\n}";
446             }
447             return evalFunction.call(object, expression);
448         } finally {
449             if (injectCommandLineAPI && inspectedWindow.console)
450                 delete inspectedWindow.console._commandLineAPI;
451         }
452     },
453
454     /**
455      * @param {Object} callFrame
456      * @return {Array.<InjectedScript.CallFrameProxy>|boolean}
457      */
458     wrapCallFrames: function(callFrame)
459     {
460         if (!callFrame)
461             return false;
462
463         var result = [];
464         var depth = 0;
465         do {
466             result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
467             callFrame = callFrame.caller;
468         } while (callFrame);
469         return result;
470     },
471
472     /**
473      * @param {Object} topCallFrame
474      * @param {string} callFrameId
475      * @param {string} expression
476      * @param {string} objectGroup
477      * @param {boolean} injectCommandLineAPI
478      * @param {boolean} returnByValue
479      * @return {*}
480      */
481     evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue)
482     {
483         var callFrame = this._callFrameForId(topCallFrame, callFrameId);
484         if (!callFrame)
485             return "Could not find call frame with given id";
486         return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue);
487     },
488
489     /**
490      * @param {Object} topCallFrame
491      * @param {string} callFrameId
492      * @return {*}
493      */
494     restartFrame: function(topCallFrame, callFrameId)
495     {
496         var callFrame = this._callFrameForId(topCallFrame, callFrameId);
497         if (!callFrame)
498             return "Could not find call frame with given id";
499         var result = callFrame.restart();
500         if (result === false)
501             result = "Restart frame is not supported"; 
502         return result;
503     },
504
505     /**
506      * @param {Object} topCallFrame
507      * @param {string} callFrameId
508      * @return {Object}
509      */
510     _callFrameForId: function(topCallFrame, callFrameId)
511     {
512         var parsedCallFrameId = eval("(" + callFrameId + ")");
513         var ordinal = parsedCallFrameId["ordinal"];
514         var callFrame = topCallFrame;
515         while (--ordinal >= 0 && callFrame)
516             callFrame = callFrame.caller;
517         return callFrame;
518     },
519
520     /**
521      * @param {Object} objectId
522      * @return {Object}
523      */
524     _objectForId: function(objectId)
525     {
526         return this._idToWrappedObject[objectId.id];
527     },
528
529     /**
530      * @param {string} objectId
531      * @return {Object}
532      */
533     findObjectById: function(objectId)
534     {
535         var parsedObjectId = this._parseObjectId(objectId);
536         return this._objectForId(parsedObjectId);
537     },
538
539     /**
540      * @param {string} objectId
541      * @return {Node}
542      */
543     nodeForObjectId: function(objectId)
544     {
545         var object = this.findObjectById(objectId);
546         if (!object || this._subtype(object) !== "node")
547             return null;
548         return object;
549     },
550
551     module: function(name)
552     {
553         return this._modules[name];
554     },
555  
556     injectModule: function(name, source)
557     {
558         delete this._modules[name];
559         var module = eval("(" + source + ")");
560         this._modules[name] = module;
561         return module;
562     },
563
564     /**
565      * @param {*} object
566      * @return {boolean}
567      */
568     _isDefined: function(object)
569     {
570         return object || this._isHTMLAllCollection(object);
571     },
572
573     /**
574      * @param {*} object
575      * @return {boolean}
576      */
577     _isHTMLAllCollection: function(object)
578     {
579         // document.all is reported as undefined, but we still want to process it.
580         return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object);
581     },
582
583     /**
584      * @param {Object=} obj
585      * @return {string?}
586      */
587     _subtype: function(obj)
588     {
589         if (obj === null)
590             return "null";
591
592         var type = typeof obj;
593         if (this.isPrimitiveValue(obj))
594             return null;
595
596         if (this._isHTMLAllCollection(obj))
597             return "array";
598
599         var preciseType = InjectedScriptHost.type(obj);
600         if (preciseType)
601             return preciseType;
602
603         // FireBug's array detection.
604         try {
605             if (typeof obj.splice === "function" && isFinite(obj.length))
606                 return "array";
607             if (Object.prototype.toString.call(obj) === "[object Arguments]" && isFinite(obj.length)) // arguments.
608                 return "array";
609         } catch (e) {
610         }
611
612         // If owning frame has navigated to somewhere else window properties will be undefined.
613         return null;
614     },
615
616     /**
617      * @param {*} obj
618      * @return {string?}
619      */
620     _describe: function(obj)
621     {
622         if (this.isPrimitiveValue(obj))
623             return null;
624
625         // Type is object, get subtype.
626         var subtype = this._subtype(obj);
627
628         if (subtype === "regexp")
629             return this._toString(obj);
630
631         if (subtype === "date")
632             return this._toString(obj);
633
634         var className = InjectedScriptHost.internalConstructorName(obj);
635         if (subtype === "array") {
636             if (typeof obj.length === "number")
637                 className += "[" + obj.length + "]";
638             return className;
639         }
640
641         // NodeList in JSC is a function, check for array prior to this.
642         if (typeof obj === "function")
643             return this._toString(obj);
644
645         if (className === "Object") {
646             // In Chromium DOM wrapper prototypes will have Object as their constructor name,
647             // get the real DOM wrapper name from the constructor property.
648             var constructorName = obj.constructor && obj.constructor.name;
649             if (constructorName)
650                 return constructorName;
651         }
652         return className;
653     },
654
655     /**
656      * @param {*} obj
657      * @return {string}
658      */
659     _toString: function(obj)
660     {
661         // We don't use String(obj) because inspectedWindow.String is undefined if owning frame navigated to another page.
662         return "" + obj;
663     }
664 }
665
666 /**
667  * @type {InjectedScript}
668  * @const
669  */
670 var injectedScript = new InjectedScript();
671
672 /**
673  * @constructor
674  * @param {*} object
675  * @param {string=} objectGroupName
676  * @param {boolean=} forceValueType
677  */
678 InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType)
679 {
680     this.type = typeof object;
681     if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
682         // We don't send undefined values over JSON.
683         if (typeof object !== "undefined")
684             this.value = object;
685
686         // Null object is object with 'null' subtype'
687         if (object === null)
688             this.subtype = "null";
689
690         // Provide user-friendly number values.
691         if (typeof object === "number")
692             this.description = object + "";
693         return;
694     }
695
696     this.objectId = injectedScript._bind(object, objectGroupName);
697     var subtype = injectedScript._subtype(object)
698     if (subtype)
699         this.subtype = subtype;
700     this.className = InjectedScriptHost.internalConstructorName(object);
701     this.description = injectedScript._describe(object);
702 }
703
704 /**
705  * @constructor
706  * @param {number} ordinal
707  * @param {Object} callFrame
708  */
709 InjectedScript.CallFrameProxy = function(ordinal, callFrame)
710 {
711     this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}";
712     this.functionName = (callFrame.type === "function" ? callFrame.functionName : "");
713     this.location = { scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column };
714     this.scopeChain = this._wrapScopeChain(callFrame);
715     this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace");
716 }
717
718 InjectedScript.CallFrameProxy.prototype = {
719     /**
720      * @param {Object} callFrame
721      * @return {Array.<Object>}
722      */
723     _wrapScopeChain: function(callFrame)
724     {
725         var scopeChain = callFrame.scopeChain;
726         var scopeChainProxy = [];
727         for (var i = 0; i < scopeChain.length; i++) {
728             var scope = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace");
729             scopeChainProxy.push(scope);
730         }
731         return scopeChainProxy;
732     }
733 }
734
735 /**
736  * @param {number} scopeTypeCode
737  * @param {*} scopeObject
738  * @param {string} groupId
739  * @return {Object}
740  */
741 InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId) {
742     const GLOBAL_SCOPE = 0;
743     const LOCAL_SCOPE = 1;
744     const WITH_SCOPE = 2;
745     const CLOSURE_SCOPE = 3;
746     const CATCH_SCOPE = 4;
747
748     var scopeTypeNames = {};
749     scopeTypeNames[GLOBAL_SCOPE] = "global";
750     scopeTypeNames[LOCAL_SCOPE] = "local";
751     scopeTypeNames[WITH_SCOPE] = "with";
752     scopeTypeNames[CLOSURE_SCOPE] = "closure";
753     scopeTypeNames[CATCH_SCOPE] = "catch";
754
755     return {
756         object: injectedScript._wrapObject(scopeObject, groupId),
757         type: scopeTypeNames[scopeTypeCode]
758     };
759 }
760
761 /**
762  * @constructor
763  * @param {CommandLineAPIImpl} commandLineAPIImpl
764  * @param {Object} callFrame
765  */
766 function CommandLineAPI(commandLineAPIImpl, callFrame)
767 {
768     /**
769      * @param {string} member
770      * @return {boolean}
771      */
772     function inScopeVariables(member)
773     {
774         if (!callFrame)
775             return false;
776
777         var scopeChain = callFrame.scopeChain;
778         for (var i = 0; i < scopeChain.length; ++i) {
779             if (member in scopeChain[i])
780                 return true;
781         }
782         return false;
783     }
784
785     for (var i = 0; i < CommandLineAPI.members_.length; ++i) {
786         var member = CommandLineAPI.members_[i];
787         if (member in inspectedWindow || inScopeVariables(member))
788             continue;
789
790         this[member] = commandLineAPIImpl[member].bind(commandLineAPIImpl);
791     }
792
793     for (var i = 0; i < 5; ++i) {
794         var member = "$" + i;
795         if (member in inspectedWindow || inScopeVariables(member))
796             continue;
797
798         this.__defineGetter__("$" + i, commandLineAPIImpl._inspectedObject.bind(commandLineAPIImpl, i));
799     }
800 }
801
802 /**
803  * @type {Array.<string>}
804  * @const
805  */
806 CommandLineAPI.members_ = [
807     "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd",
808     "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners"
809 ];
810
811 /**
812  * @constructor
813  */
814 function CommandLineAPIImpl()
815 {
816 }
817
818 CommandLineAPIImpl.prototype = {
819     $: function()
820     {
821         return document.getElementById.apply(document, arguments)
822     },
823
824     $$: function()
825     {
826         return document.querySelectorAll.apply(document, arguments)
827     },
828
829     $x: function(xpath, context)
830     {
831         var doc = (context && context.ownerDocument) || inspectedWindow.document;
832         var result = doc.evaluate(xpath, context || doc, null, XPathResult.ANY_TYPE, null);
833         switch (result.resultType) {
834         case XPathResult.NUMBER_TYPE:
835             return result.numberValue;
836         case XPathResult.STRING_TYPE:
837             return result.stringValue;
838         case XPathResult.BOOLEAN_TYPE:
839             return result.booleanValue;
840         default:
841             var nodes = [];
842             var node;
843             while (node = result.iterateNext())
844                 nodes.push(node);
845             return nodes;
846         }
847     },
848
849     dir: function()
850     {
851         return console.dir.apply(console, arguments)
852     },
853
854     dirxml: function()
855     {
856         return console.dirxml.apply(console, arguments)
857     },
858
859     keys: function(object)
860     {
861         return Object.keys(object);
862     },
863
864     values: function(object)
865     {
866         var result = [];
867         for (var key in object)
868             result.push(object[key]);
869         return result;
870     },
871
872     profile: function()
873     {
874         return console.profile.apply(console, arguments)
875     },
876
877     profileEnd: function()
878     {
879         return console.profileEnd.apply(console, arguments)
880     },
881
882     /**
883      * @param {Object} object
884      * @param {Array.<string>|string=} types
885      */
886     monitorEvents: function(object, types)
887     {
888         if (!object || !object.addEventListener || !object.removeEventListener)
889             return;
890         types = this._normalizeEventTypes(types);
891         for (var i = 0; i < types.length; ++i) {
892             object.removeEventListener(types[i], this._logEvent, false);
893             object.addEventListener(types[i], this._logEvent, false);
894         }
895     },
896
897     /**
898      * @param {Object} object
899      * @param {Array.<string>|string=} types
900      */
901     unmonitorEvents: function(object, types)
902     {
903         if (!object || !object.addEventListener || !object.removeEventListener)
904             return;
905         types = this._normalizeEventTypes(types);
906         for (var i = 0; i < types.length; ++i)
907             object.removeEventListener(types[i], this._logEvent, false);
908     },
909
910     /**
911      * @param {*} object
912      * @return {*}
913      */
914     inspect: function(object)
915     {
916         return injectedScript._inspect(object);
917     },
918
919     copy: function(object)
920     {
921         if (injectedScript._subtype(object) === "node")
922             object = object.outerHTML;
923         InjectedScriptHost.copyText(object);
924     },
925
926     clear: function()
927     {
928         InjectedScriptHost.clearConsoleMessages();
929     },
930
931     /**
932      * @param {Node} node
933      */
934     getEventListeners: function(node)
935     {
936         return InjectedScriptHost.getEventListeners(node);
937     },
938
939     /**
940      * @param {number} num
941      */
942     _inspectedObject: function(num)
943     {
944         return InjectedScriptHost.inspectedObject(num);
945     },
946
947     /**
948      * @param {Array.<string>|string=} types
949      * @return {Array.<string>}
950      */
951     _normalizeEventTypes: function(types)
952     {
953         if (typeof types === "undefined")
954             types = [ "mouse", "key", "touch", "control", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll", "search", "devicemotion", "deviceorientation" ];
955         else if (typeof types === "string")
956             types = [ types ];
957
958         var result = [];
959         for (var i = 0; i < types.length; i++) {
960             if (types[i] === "mouse")
961                 result.splice(0, 0, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout", "mousewheel");
962             else if (types[i] === "key")
963                 result.splice(0, 0, "keydown", "keyup", "keypress", "textInput");
964             else if (types[i] === "touch")
965                 result.splice(0, 0, "touchstart", "touchmove", "touchend", "touchcancel");
966             else if (types[i] === "control")
967                 result.splice(0, 0, "resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset");
968             else
969                 result.push(types[i]);
970         }
971         return result;
972     },
973
974     /**
975      * @param {Event} event
976      */
977     _logEvent: function(event)
978     {
979         console.log(event.type, event);
980     }
981 }
982
983 injectedScript._commandLineAPIImpl = new CommandLineAPIImpl();
984 return injectedScript;
985 })