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.
37var Object = {}.constructor;
38
39/**
40 * @param {Arguments} array
41 * @param {number=} index
42 * @return {Array.<*>}
43 */
44function 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 */
58function 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 */
78var 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 */
91InjectedScript.primitiveTypes = {
92    undefined: true,
93    boolean: true,
94    number: true,
95    string: true
96}
97
98InjectedScript.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 (!details)
349            return "Cannot resolve function details.";
350        if ("rawScopes" in details) {
351            var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id];
352            var rawScopes = details.rawScopes;
353            var scopes = [];
354            delete details.rawScopes;
355            for (var i = 0; i < rawScopes.length; i++)
356                scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName));
357            details.scopeChain = scopes;
358        }
359        return details;
360    },
361
362    /**
363     * @param {string} objectId
364     */
365    releaseObject: function(objectId)
366    {
367        var parsedObjectId = this._parseObjectId(objectId);
368        this._releaseObject(parsedObjectId.id);
369    },
370
371    /**
372     * @param {string} id
373     */
374    _releaseObject: function(id)
375    {
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     * @throws {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        var commandLineAPI = injectCommandLineAPI ? new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null) : null;
556
557        if (isEvalOnCallFrame) {
558            // We can only use this approach if the evaluate function is the true 'eval'. That allows us to use it with
559            // the 'eval' identifier when calling it. Using 'eval' grants access to the local scope of the closure we
560            // create that provides the command line APIs.
561
562            var parameters = [InjectedScriptHost.evaluate, expression];
563            var expressionFunctionBody = "var __originalEval = window.eval; window.eval = __eval; try { return eval(__currentExpression); } finally { window.eval = __originalEval; }";
564
565            if (commandLineAPI) {
566                // To avoid using a 'with' statement (which fails in strict mode and requires injecting the API object)
567                // we instead create a closure where we evaluate the expression. The command line APIs are passed as
568                // parameters to the closure so they are in scope but not injected. This allows the code evaluated in
569                // the console to stay in strict mode (if is was already set), or to get strict mode by prefixing
570                // expressions with 'use strict';.
571
572                var parameterNames = Object.getOwnPropertyNames(commandLineAPI);
573                for (var i = 0; i < parameterNames.length; ++i)
574                    parameters.push(commandLineAPI[parameterNames[i]]);
575
576                var expressionFunctionString = "(function(__eval, __currentExpression, " + parameterNames.join(", ") + ") { " + expressionFunctionBody + " })";
577            } else {
578                // Use a closure in this case too to keep the same behavior of 'var' being captured by the closure instead
579                // of leaking out into the calling scope.
580                var expressionFunctionString = "(function(__eval, __currentExpression) { " + expressionFunctionBody + " })";
581            }
582
583            // Bind 'this' to the function expression using another closure instead of Function.prototype.bind. This ensures things will work if the page replaces bind.
584            var boundExpressionFunctionString = "(function(__function, __thisObject) { return function() { return __function.apply(__thisObject, arguments) }; })(" + expressionFunctionString + ", this)";
585            var expressionFunction = evalFunction.call(object, boundExpressionFunctionString);
586            var result = expressionFunction.apply(null, parameters);
587
588            if (objectGroup === "console")
589                this._lastResult = result;
590
591            return result;
592        }
593
594        // When not evaluating on a call frame we use a 'with' statement to allow var and function statements to leak
595        // into the global scope. This allow them to stick around between evaluations.
596
597        try {
598            if (commandLineAPI && inspectedWindow.console) {
599                inspectedWindow.console.__commandLineAPI = commandLineAPI;
600                expression = "with ((window && window.console && window.console.__commandLineAPI) || {}) { " + expression + "\n}";
601            }
602
603            var result = evalFunction.call(object, expression);
604
605            if (objectGroup === "console")
606                this._lastResult = result;
607
608            return result;
609        } finally {
610            if (commandLineAPI && inspectedWindow.console)
611                delete inspectedWindow.console.__commandLineAPI;
612        }
613    },
614
615    /**
616     * @param {Object} callFrame
617     * @return {Array.<InjectedScript.CallFrameProxy>|boolean}
618     */
619    wrapCallFrames: function(callFrame)
620    {
621        if (!callFrame)
622            return false;
623
624        var result = [];
625        var depth = 0;
626        do {
627            result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
628            callFrame = callFrame.caller;
629        } while (callFrame);
630        return result;
631    },
632
633    /**
634     * @param {Object} topCallFrame
635     * @param {string} callFrameId
636     * @param {string} expression
637     * @param {string} objectGroup
638     * @param {boolean} injectCommandLineAPI
639     * @param {boolean} returnByValue
640     * @param {boolean} generatePreview
641     * @return {*}
642     */
643    evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview)
644    {
645        var callFrame = this._callFrameForId(topCallFrame, callFrameId);
646        if (!callFrame)
647            return "Could not find call frame with given id";
648        return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview);
649    },
650
651    /**
652     * @param {Object} topCallFrame
653     * @param {string} callFrameId
654     * @return {*}
655     */
656    restartFrame: function(topCallFrame, callFrameId)
657    {
658        var callFrame = this._callFrameForId(topCallFrame, callFrameId);
659        if (!callFrame)
660            return "Could not find call frame with given id";
661        var result = callFrame.restart();
662        if (result === false)
663            result = "Restart frame is not supported";
664        return result;
665    },
666
667    /**
668     * Either callFrameId or functionObjectId must be specified.
669     * @param {Object} topCallFrame
670     * @param {string|boolean} callFrameId or false
671     * @param {string|boolean} functionObjectId or false
672     * @param {number} scopeNumber
673     * @param {string} variableName
674     * @param {string} newValueJsonString RuntimeAgent.CallArgument structure serialized as string
675     * @return {string|undefined} undefined if success or an error message
676     */
677    setVariableValue: function(topCallFrame, callFrameId, functionObjectId, scopeNumber, variableName, newValueJsonString)
678    {
679        var setter;
680        if (typeof callFrameId === "string") {
681            var callFrame = this._callFrameForId(topCallFrame, callFrameId);
682            if (!callFrame)
683                return "Could not find call frame with given id";
684            setter = callFrame.setVariableValue.bind(callFrame);
685        } else {
686            var parsedFunctionId = this._parseObjectId(/** @type {string} */(functionObjectId));
687            var func = this._objectForId(parsedFunctionId);
688            if (typeof func !== "function")
689                return "Cannot resolve function by id.";
690            setter = InjectedScriptHost.setFunctionVariableValue.bind(InjectedScriptHost, func);
691        }
692        var newValueJson;
693        try {
694            newValueJson = InjectedScriptHost.evaluate("(" + newValueJsonString + ")");
695        } catch (e) {
696            return "Failed to parse new value JSON " + newValueJsonString + " : " + e;
697        }
698        var resolvedValue;
699        try {
700            resolvedValue = this._resolveCallArgument(newValueJson);
701        } catch (e) {
702            return String(e);
703        }
704        try {
705            setter(scopeNumber, variableName, resolvedValue);
706        } catch (e) {
707            return "Failed to change variable value: " + e;
708        }
709        return undefined;
710    },
711
712    /**
713     * @param {Object} topCallFrame
714     * @param {string} callFrameId
715     * @return {Object}
716     */
717    _callFrameForId: function(topCallFrame, callFrameId)
718    {
719        var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")");
720        var ordinal = parsedCallFrameId["ordinal"];
721        var callFrame = topCallFrame;
722        while (--ordinal >= 0 && callFrame)
723            callFrame = callFrame.caller;
724        return callFrame;
725    },
726
727    /**
728     * @param {Object} objectId
729     * @return {Object}
730     */
731    _objectForId: function(objectId)
732    {
733        return this._idToWrappedObject[objectId.id];
734    },
735
736    /**
737     * @param {string} objectId
738     * @return {Object}
739     */
740    findObjectById: function(objectId)
741    {
742        var parsedObjectId = this._parseObjectId(objectId);
743        return this._objectForId(parsedObjectId);
744    },
745
746    /**
747     * @param {string} objectId
748     * @return {Node}
749     */
750    nodeForObjectId: function(objectId)
751    {
752        var object = this.findObjectById(objectId);
753        if (!object || this._subtype(object) !== "node")
754            return null;
755        return /** @type {Node} */ (object);
756    },
757
758    /**
759     * @param {string} name
760     * @return {Object}
761     */
762    module: function(name)
763    {
764        return this._modules[name];
765    },
766
767    /**
768     * @param {string} name
769     * @param {string} source
770     * @return {Object}
771     */
772    injectModule: function(name, source)
773    {
774        delete this._modules[name];
775        var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")");
776        if (typeof moduleFunction !== "function") {
777            inspectedWindow.console.error("Web Inspector error: A function was expected for module %s evaluation", name);
778            return null;
779        }
780        var module = moduleFunction.call(inspectedWindow, InjectedScriptHost, inspectedWindow, injectedScriptId);
781        this._modules[name] = module;
782        return module;
783    },
784
785    /**
786     * @param {*} object
787     * @return {boolean}
788     */
789    _isDefined: function(object)
790    {
791        return !!object || this._isHTMLAllCollection(object);
792    },
793
794    /**
795     * @param {*} object
796     * @return {boolean}
797     */
798    _isHTMLAllCollection: function(object)
799    {
800        // document.all is reported as undefined, but we still want to process it.
801        return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object);
802    },
803
804    /**
805     * @param {Object=} obj
806     * @return {string?}
807     */
808    _subtype: function(obj)
809    {
810        if (obj === null)
811            return "null";
812
813        if (this.isPrimitiveValue(obj))
814            return null;
815
816        if (this._isHTMLAllCollection(obj))
817            return "array";
818
819        var preciseType = InjectedScriptHost.type(obj);
820        if (preciseType)
821            return preciseType;
822
823        // FireBug's array detection.
824        try {
825            if (typeof obj.splice === "function" && isFinite(obj.length))
826                return "array";
827            if (Object.prototype.toString.call(obj) === "[object Arguments]" && isFinite(obj.length)) // arguments.
828                return "array";
829        } catch (e) {
830        }
831
832        // If owning frame has navigated to somewhere else window properties will be undefined.
833        return null;
834    },
835
836    /**
837     * @param {*} obj
838     * @return {string?}
839     */
840    _describe: function(obj)
841    {
842        if (this.isPrimitiveValue(obj))
843            return null;
844
845        obj = /** @type {Object} */ (obj);
846
847        // Type is object, get subtype.
848        var subtype = this._subtype(obj);
849
850        if (subtype === "regexp")
851            return this._toString(obj);
852
853        if (subtype === "date")
854            return this._toString(obj);
855
856        if (subtype === "node") {
857            var description = obj.nodeName.toLowerCase();
858            switch (obj.nodeType) {
859            case 1 /* Node.ELEMENT_NODE */:
860                description += obj.id ? "#" + obj.id : "";
861                var className = obj.className;
862                description += className ? "." + className : "";
863                break;
864            case 10 /*Node.DOCUMENT_TYPE_NODE */:
865                description = "<!DOCTYPE " + description + ">";
866                break;
867            }
868            return description;
869        }
870
871        var className = InjectedScriptHost.internalConstructorName(obj);
872        if (subtype === "array") {
873            if (typeof obj.length === "number")
874                className += "[" + obj.length + "]";
875            return className;
876        }
877
878        // NodeList in JSC is a function, check for array prior to this.
879        if (typeof obj === "function")
880            return this._toString(obj);
881
882        if (className === "Object") {
883            // In Chromium DOM wrapper prototypes will have Object as their constructor name,
884            // get the real DOM wrapper name from the constructor property.
885            var constructorName = obj.constructor && obj.constructor.name;
886            if (constructorName)
887                return constructorName;
888        }
889        return className;
890    },
891
892    /**
893     * @param {*} obj
894     * @return {string}
895     */
896    _toString: function(obj)
897    {
898        // We don't use String(obj) because inspectedWindow.String is undefined if owning frame navigated to another page.
899        return "" + obj;
900    }
901}
902
903/**
904 * @type {InjectedScript}
905 * @const
906 */
907var injectedScript = new InjectedScript();
908
909/**
910 * @constructor
911 * @param {*} object
912 * @param {string=} objectGroupName
913 * @param {boolean=} forceValueType
914 * @param {boolean=} generatePreview
915 * @param {?Array.<string>=} columnNames
916 */
917InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames)
918{
919    this.type = typeof object;
920    if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
921        // We don't send undefined values over JSON.
922        if (typeof object !== "undefined")
923            this.value = object;
924
925        // Null object is object with 'null' subtype'
926        if (object === null)
927            this.subtype = "null";
928
929        // Provide user-friendly number values.
930        if (typeof object === "number")
931            this.description = object + "";
932        return;
933    }
934
935    object = /** @type {Object} */ (object);
936
937    this.objectId = injectedScript._bind(object, objectGroupName);
938    var subtype = injectedScript._subtype(object);
939    if (subtype)
940        this.subtype = subtype;
941    this.className = InjectedScriptHost.internalConstructorName(object);
942    this.description = injectedScript._describe(object);
943
944    if (generatePreview && (this.type === "object" || injectedScript._isHTMLAllCollection(object)))
945        this.preview = this._generatePreview(object, undefined, columnNames);
946}
947
948InjectedScript.RemoteObject.prototype = {
949    /**
950     * @param {Object} object
951     * @param {Array.<string>=} firstLevelKeys
952     * @param {?Array.<string>=} secondLevelKeys
953     * @return {Object} preview
954     */
955    _generatePreview: function(object, firstLevelKeys, secondLevelKeys)
956    {
957        var preview = {};
958        preview.lossless = true;
959        preview.overflow = false;
960        preview.properties = [];
961
962        var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys;
963        var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
964
965        var propertiesThreshold = {
966            properties: isTableRowsRequest ? 1000 : Math.max(5, firstLevelKeysCount),
967            indexes: isTableRowsRequest ? 1000 : Math.max(100, firstLevelKeysCount)
968        };
969        for (var o = object; injectedScript._isDefined(o); o = o.__proto__)
970            this._generateProtoPreview(o, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys);
971        return preview;
972    },
973
974    /**
975     * @param {Object} object
976     * @param {Object} preview
977     * @param {Object} propertiesThreshold
978     * @param {Array.<string>=} firstLevelKeys
979     * @param {Array.<string>=} secondLevelKeys
980     */
981    _generateProtoPreview: function(object, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys)
982    {
983        var propertyNames = firstLevelKeys ? firstLevelKeys : Object.keys(/** @type {!Object} */(object));
984        try {
985            for (var i = 0; i < propertyNames.length; ++i) {
986                if (!propertiesThreshold.properties || !propertiesThreshold.indexes) {
987                    preview.overflow = true;
988                    preview.lossless = false;
989                    break;
990                }
991                var name = propertyNames[i];
992                if (this.subtype === "array" && name === "length")
993                    continue;
994
995                var descriptor = Object.getOwnPropertyDescriptor(/** @type {!Object} */(object), name);
996                if (!("value" in descriptor) || !descriptor.enumerable) {
997                    preview.lossless = false;
998                    continue;
999                }
1000
1001                var value = descriptor.value;
1002                if (value === null) {
1003                    this._appendPropertyPreview(preview, { name: name, type: "object", value: "null" }, propertiesThreshold);
1004                    continue;
1005                }
1006
1007                const maxLength = 100;
1008                var type = typeof value;
1009
1010                if (InjectedScript.primitiveTypes[type]) {
1011                    if (type === "string") {
1012                        if (value.length > maxLength) {
1013                            value = this._abbreviateString(value, maxLength, true);
1014                            preview.lossless = false;
1015                        }
1016                        value = value.replace(/\n/g, "\u21B5");
1017                    }
1018                    this._appendPropertyPreview(preview, { name: name, type: type, value: value + "" }, propertiesThreshold);
1019                    continue;
1020                }
1021
1022                if (secondLevelKeys === null || secondLevelKeys) {
1023                    var subPreview = this._generatePreview(value, secondLevelKeys || undefined);
1024                    var property = { name: name, type: type, valuePreview: subPreview };
1025                    this._appendPropertyPreview(preview, property, propertiesThreshold);
1026                    if (!subPreview.lossless)
1027                        preview.lossless = false;
1028                    if (subPreview.overflow)
1029                        preview.overflow = true;
1030                    continue;
1031                }
1032
1033                preview.lossless = false;
1034
1035                var subtype = injectedScript._subtype(value);
1036                var description = "";
1037                if (type !== "function")
1038                    description = this._abbreviateString(/** @type {string} */ (injectedScript._describe(value)), maxLength, subtype === "regexp");
1039
1040                var property = { name: name, type: type, value: description };
1041                if (subtype)
1042                    property.subtype = subtype;
1043                this._appendPropertyPreview(preview, property, propertiesThreshold);
1044            }
1045        } catch (e) {
1046        }
1047    },
1048
1049    /**
1050     * @param {Object} preview
1051     * @param {Object} property
1052     * @param {Object} propertiesThreshold
1053     */
1054    _appendPropertyPreview: function(preview, property, propertiesThreshold)
1055    {
1056        if (isNaN(property.name))
1057            propertiesThreshold.properties--;
1058        else
1059            propertiesThreshold.indexes--;
1060        preview.properties.push(property);
1061    },
1062
1063    /**
1064     * @param {string} string
1065     * @param {number} maxLength
1066     * @param {boolean=} middle
1067     * @returns
1068     */
1069    _abbreviateString: function(string, maxLength, middle)
1070    {
1071        if (string.length <= maxLength)
1072            return string;
1073        if (middle) {
1074            var leftHalf = maxLength >> 1;
1075            var rightHalf = maxLength - leftHalf - 1;
1076            return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf);
1077        }
1078        return string.substr(0, maxLength) + "\u2026";
1079    }
1080}
1081/**
1082 * @constructor
1083 * @param {number} ordinal
1084 * @param {Object} callFrame
1085 */
1086InjectedScript.CallFrameProxy = function(ordinal, callFrame)
1087{
1088    this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}";
1089    this.functionName = (callFrame.type === "function" ? callFrame.functionName : "");
1090    this.location = { scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column };
1091    this.scopeChain = this._wrapScopeChain(callFrame);
1092    this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace");
1093}
1094
1095InjectedScript.CallFrameProxy.prototype = {
1096    /**
1097     * @param {Object} callFrame
1098     * @return {!Array.<DebuggerAgent.Scope>}
1099     */
1100    _wrapScopeChain: function(callFrame)
1101    {
1102        var scopeChain = callFrame.scopeChain;
1103        var scopeChainProxy = [];
1104        for (var i = 0; i < scopeChain.length; i++) {
1105            var scope = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace");
1106            scopeChainProxy.push(scope);
1107        }
1108        return scopeChainProxy;
1109    }
1110}
1111
1112/**
1113 * @param {number} scopeTypeCode
1114 * @param {*} scopeObject
1115 * @param {string} groupId
1116 * @return {!DebuggerAgent.Scope}
1117 */
1118InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId) {
1119    const GLOBAL_SCOPE = 0;
1120    const LOCAL_SCOPE = 1;
1121    const WITH_SCOPE = 2;
1122    const CLOSURE_SCOPE = 3;
1123    const CATCH_SCOPE = 4;
1124
1125    /** @type {!Object.<number, string>} */
1126    var scopeTypeNames = {};
1127    scopeTypeNames[GLOBAL_SCOPE] = "global";
1128    scopeTypeNames[LOCAL_SCOPE] = "local";
1129    scopeTypeNames[WITH_SCOPE] = "with";
1130    scopeTypeNames[CLOSURE_SCOPE] = "closure";
1131    scopeTypeNames[CATCH_SCOPE] = "catch";
1132
1133    return {
1134        object: injectedScript._wrapObject(scopeObject, groupId),
1135        type: /** @type {DebuggerAgent.ScopeType} */ (scopeTypeNames[scopeTypeCode])
1136    };
1137}
1138
1139/**
1140 * @constructor
1141 * @param {CommandLineAPIImpl} commandLineAPIImpl
1142 * @param {Object} callFrame
1143 */
1144function CommandLineAPI(commandLineAPIImpl, callFrame)
1145{
1146    /**
1147     * @param {string} member
1148     * @return {boolean}
1149     */
1150    function inScopeVariables(member)
1151    {
1152        if (!callFrame)
1153            return false;
1154
1155        var scopeChain = callFrame.scopeChain;
1156        for (var i = 0; i < scopeChain.length; ++i) {
1157            if (member in scopeChain[i])
1158                return true;
1159        }
1160        return false;
1161    }
1162
1163    /**
1164     * @param {string} name The name of the method for which a toString method should be generated.
1165     * @return {function():string}
1166     */
1167    function customToStringMethod(name)
1168    {
1169        return function () { return "function " + name + "() { [Command Line API] }"; };
1170    }
1171
1172    for (var i = 0; i < CommandLineAPI.members_.length; ++i) {
1173        var member = CommandLineAPI.members_[i];
1174        if (member in inspectedWindow || inScopeVariables(member))
1175            continue;
1176
1177        this[member] = bind(commandLineAPIImpl[member], commandLineAPIImpl);
1178        this[member].toString = customToStringMethod(member);
1179    }
1180
1181    for (var i = 0; i < 5; ++i) {
1182        var member = "$" + i;
1183        if (member in inspectedWindow || inScopeVariables(member))
1184            continue;
1185
1186        this.__defineGetter__("$" + i, bind(commandLineAPIImpl._inspectedObject, commandLineAPIImpl, i));
1187    }
1188
1189    this.$_ = injectedScript._lastResult;
1190}
1191
1192// NOTE: Please keep the list of API methods below snchronized to that in WebInspector.RuntimeModel!
1193/**
1194 * @type {Array.<string>}
1195 * @const
1196 */
1197CommandLineAPI.members_ = [
1198    "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd",
1199    "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners"
1200];
1201
1202/**
1203 * @constructor
1204 */
1205function CommandLineAPIImpl()
1206{
1207}
1208
1209CommandLineAPIImpl.prototype = {
1210    /**
1211     * @param {string} selector
1212     * @param {Node=} start
1213     */
1214    $: function (selector, start)
1215    {
1216        if (this._canQuerySelectorOnNode(start))
1217            return start.querySelector(selector);
1218
1219        var result = inspectedWindow.document.querySelector(selector);
1220        if (result)
1221            return result;
1222        if (selector && selector[0] !== "#") {
1223            result = inspectedWindow.document.getElementById(selector);
1224            if (result) {
1225                inspectedWindow.console.warn("The console function $() has changed from $=getElementById(id) to $=querySelector(selector). You might try $(\"#%s\")", selector );
1226                return null;
1227            }
1228        }
1229        return result;
1230    },
1231
1232    /**
1233     * @param {string} selector
1234     * @param {Node=} start
1235     */
1236    $$: function (selector, start)
1237    {
1238        if (this._canQuerySelectorOnNode(start))
1239            return start.querySelectorAll(selector);
1240        return inspectedWindow.document.querySelectorAll(selector);
1241    },
1242
1243    /**
1244     * @param {Node=} node
1245     * @return {boolean}
1246     */
1247    _canQuerySelectorOnNode: function(node)
1248    {
1249        return !!node && InjectedScriptHost.type(node) === "node" && (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE);
1250    },
1251
1252    /**
1253     * @param {string} xpath
1254     * @param {Node=} context
1255     */
1256    $x: function(xpath, context)
1257    {
1258        var doc = (context && context.ownerDocument) || inspectedWindow.document;
1259        var result = doc.evaluate(xpath, context || doc, null, XPathResult.ANY_TYPE, null);
1260        switch (result.resultType) {
1261        case XPathResult.NUMBER_TYPE:
1262            return result.numberValue;
1263        case XPathResult.STRING_TYPE:
1264            return result.stringValue;
1265        case XPathResult.BOOLEAN_TYPE:
1266            return result.booleanValue;
1267        default:
1268            var nodes = [];
1269            var node;
1270            while (node = result.iterateNext())
1271                nodes.push(node);
1272            return nodes;
1273        }
1274    },
1275
1276    dir: function()
1277    {
1278        return inspectedWindow.console.dir.apply(inspectedWindow.console, arguments)
1279    },
1280
1281    dirxml: function()
1282    {
1283        return inspectedWindow.console.dirxml.apply(inspectedWindow.console, arguments)
1284    },
1285
1286    keys: function(object)
1287    {
1288        return Object.keys(object);
1289    },
1290
1291    values: function(object)
1292    {
1293        var result = [];
1294        for (var key in object)
1295            result.push(object[key]);
1296        return result;
1297    },
1298
1299    profile: function()
1300    {
1301        return inspectedWindow.console.profile.apply(inspectedWindow.console, arguments)
1302    },
1303
1304    profileEnd: function()
1305    {
1306        return inspectedWindow.console.profileEnd.apply(inspectedWindow.console, arguments)
1307    },
1308
1309    /**
1310     * @param {Object} object
1311     * @param {Array.<string>|string=} types
1312     */
1313    monitorEvents: function(object, types)
1314    {
1315        if (!object || !object.addEventListener || !object.removeEventListener)
1316            return;
1317        types = this._normalizeEventTypes(types);
1318        for (var i = 0; i < types.length; ++i) {
1319            object.removeEventListener(types[i], this._logEvent, false);
1320            object.addEventListener(types[i], this._logEvent, false);
1321        }
1322    },
1323
1324    /**
1325     * @param {Object} object
1326     * @param {Array.<string>|string=} types
1327     */
1328    unmonitorEvents: function(object, types)
1329    {
1330        if (!object || !object.addEventListener || !object.removeEventListener)
1331            return;
1332        types = this._normalizeEventTypes(types);
1333        for (var i = 0; i < types.length; ++i)
1334            object.removeEventListener(types[i], this._logEvent, false);
1335    },
1336
1337    /**
1338     * @param {*} object
1339     * @return {*}
1340     */
1341    inspect: function(object)
1342    {
1343        return injectedScript._inspect(object);
1344    },
1345
1346    copy: function(object)
1347    {
1348        if (injectedScript._subtype(object) === "node")
1349            object = object.outerHTML;
1350        InjectedScriptHost.copyText(object);
1351    },
1352
1353    clear: function()
1354    {
1355        InjectedScriptHost.clearConsoleMessages();
1356    },
1357
1358    /**
1359     * @param {Node} node
1360     */
1361    getEventListeners: function(node)
1362    {
1363        return InjectedScriptHost.getEventListeners(node);
1364    },
1365
1366    /**
1367     * @param {number} num
1368     */
1369    _inspectedObject: function(num)
1370    {
1371        return InjectedScriptHost.inspectedObject(num);
1372    },
1373
1374    /**
1375     * @param {Array.<string>|string=} types
1376     * @return {Array.<string>}
1377     */
1378    _normalizeEventTypes: function(types)
1379    {
1380        if (typeof types === "undefined")
1381            types = [ "mouse", "key", "touch", "control", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll", "search", "devicemotion", "deviceorientation" ];
1382        else if (typeof types === "string")
1383            types = [ types ];
1384
1385        var result = [];
1386        for (var i = 0; i < types.length; i++) {
1387            if (types[i] === "mouse")
1388                result.splice(0, 0, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout", "mousewheel");
1389            else if (types[i] === "key")
1390                result.splice(0, 0, "keydown", "keyup", "keypress", "textInput");
1391            else if (types[i] === "touch")
1392                result.splice(0, 0, "touchstart", "touchmove", "touchend", "touchcancel");
1393            else if (types[i] === "control")
1394                result.splice(0, 0, "resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset");
1395            else
1396                result.push(types[i]);
1397        }
1398        return result;
1399    },
1400
1401    /**
1402     * @param {Event} event
1403     */
1404    _logEvent: function(event)
1405    {
1406        inspectedWindow.console.log(event.type, event);
1407    }
1408}
1409
1410injectedScript._commandLineAPIImpl = new CommandLineAPIImpl();
1411return injectedScript;
1412})
1413