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