029e56d3b502f790d142f41bf85a65591b1ec31b
[WebKit-https.git] / Source / JavaScriptCore / inspector / InjectedScriptSource.js
1 /*
2  * Copyright (C) 2007, 2014 Apple Inc.  All rights reserved.
3  * Copyright (C) 2013 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 //# sourceURL=__WebInspectorInjectedScript__
31
32 (function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) {
33
34 // Protect against Object overwritten by the user code.
35 var Object = {}.constructor;
36
37 function toString(obj)
38 {
39     return String(obj);
40 }
41
42 function isUInt32(obj)
43 {
44     if (typeof obj === "number")
45         return obj >>> 0 === obj && (obj > 0 || 1 / obj > 0);
46     return "" + (obj >>> 0) === obj;
47 }
48
49 function isSymbol(obj)
50 {
51     return typeof obj === "symbol";
52 }
53
54 var InjectedScript = function()
55 {
56     this._lastBoundObjectId = 1;
57     this._idToWrappedObject = {};
58     this._idToObjectGroupName = {};
59     this._objectGroups = {};
60     this._modules = {};
61     this._nextSavedResultIndex = 1;
62     this._savedResults = [];
63 }
64
65 InjectedScript.primitiveTypes = {
66     undefined: true,
67     boolean: true,
68     number: true,
69     string: true,
70 }
71
72 InjectedScript.CollectionMode = {
73     OwnProperties: 1 << 0,          // own properties.
74     NativeGetterProperties: 1 << 1, // native getter properties in the prototype chain.
75     AllProperties: 1 << 2,          // all properties in the prototype chain.
76 }
77
78 InjectedScript.prototype = {
79     isPrimitiveValue: function(object)
80     {
81         // FIXME(33716): typeof document.all is always 'undefined'.
82         return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
83     },
84
85     wrapObject: function(object, groupName, canAccessInspectedGlobalObject, generatePreview)
86     {
87         if (canAccessInspectedGlobalObject)
88             return this._wrapObject(object, groupName, false, generatePreview);
89         return this._fallbackWrapper(object);
90     },
91
92     setExceptionValue: function(value)
93     {
94         this._exceptionValue = value;
95     },
96
97     clearExceptionValue: function()
98     {
99         delete this._exceptionValue;
100     },
101
102     _fallbackWrapper: function(object)
103     {
104         var result = {};
105         result.type = typeof object;
106         if (this.isPrimitiveValue(object))
107             result.value = object;
108         else
109             result.description = toString(object);
110         return result;
111     },
112
113     wrapTable: function(canAccessInspectedGlobalObject, table, columns)
114     {
115         if (!canAccessInspectedGlobalObject)
116             return this._fallbackWrapper(table);
117
118         // FIXME: Currently columns are ignored. Instead, the frontend filters all
119         // properties based on the provided column names and in the provided order.
120         // Should we filter here too?
121
122         var columnNames = null;
123         if (typeof columns === "string")
124             columns = [columns];
125
126         if (InjectedScriptHost.subtype(columns) === "array") {
127             columnNames = [];
128             for (var i = 0; i < columns.length; ++i)
129                 columnNames.push(toString(columns[i]));
130         }
131
132         return this._wrapObject(table, "console", false, true, columnNames);
133     },
134
135     inspectObject: function(object)
136     {
137         if (this._commandLineAPIImpl)
138             this._commandLineAPIImpl.inspect(object);
139     },
140
141     _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames)
142     {
143         try {
144             return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType, generatePreview, columnNames);
145         } catch (e) {
146             try {
147                 var description = injectedScript._describe(e);
148             } catch (ex) {
149                 var description = "<failed to convert exception to string>";
150             }
151             return new InjectedScript.RemoteObject(description);
152         }
153     },
154
155     _bind: function(object, objectGroupName)
156     {
157         var id = this._lastBoundObjectId++;
158         this._idToWrappedObject[id] = object;
159         var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}";
160         if (objectGroupName) {
161             var group = this._objectGroups[objectGroupName];
162             if (!group) {
163                 group = [];
164                 this._objectGroups[objectGroupName] = group;
165             }
166             group.push(id);
167             this._idToObjectGroupName[id] = objectGroupName;
168         }
169         return objectId;
170     },
171
172     _parseObjectId: function(objectId)
173     {
174         return InjectedScriptHost.evaluate("(" + objectId + ")");
175     },
176
177     releaseObjectGroup: function(objectGroupName)
178     {
179         if (objectGroupName === "console") {
180             delete this._lastResult;
181             this._nextSavedResultIndex = 1;
182             this._savedResults = [];
183         }
184
185         var group = this._objectGroups[objectGroupName];
186         if (!group)
187             return;
188
189         for (var i = 0; i < group.length; i++)
190             this._releaseObject(group[i]);
191
192         delete this._objectGroups[objectGroupName];
193     },
194
195     dispatch: function(methodName, args)
196     {
197         var argsArray = InjectedScriptHost.evaluate("(" + args + ")");
198         var result = this[methodName].apply(this, argsArray);
199         if (typeof result === "undefined") {
200             if (inspectedGlobalObject.console)
201                 inspectedGlobalObject.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
202             result = null;
203         }
204         return result;
205     },
206
207     _getProperties: function(objectId, collectionMode, generatePreview)
208     {
209         var parsedObjectId = this._parseObjectId(objectId);
210         var object = this._objectForId(parsedObjectId);
211         var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
212
213         if (!this._isDefined(object))
214             return false;
215
216         if (isSymbol(object))
217             return false;
218
219         var descriptors = this._propertyDescriptors(object, collectionMode);
220
221         // Go over properties, wrap object values.
222         for (var i = 0; i < descriptors.length; ++i) {
223             var descriptor = descriptors[i];
224             if ("get" in descriptor)
225                 descriptor.get = this._wrapObject(descriptor.get, objectGroupName);
226             if ("set" in descriptor)
227                 descriptor.set = this._wrapObject(descriptor.set, objectGroupName);
228             if ("value" in descriptor)
229                 descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
230             if (!("configurable" in descriptor))
231                 descriptor.configurable = false;
232             if (!("enumerable" in descriptor))
233                 descriptor.enumerable = false;
234         }
235
236         return descriptors;
237     },
238
239     getProperties: function(objectId, ownProperties, generatePreview)
240     {
241         var collectionMode = ownProperties ? InjectedScript.CollectionMode.OwnProperties : InjectedScript.CollectionMode.AllProperties;
242         return this._getProperties(objectId, collectionMode, generatePreview);
243     },
244
245     getDisplayableProperties: function(objectId, generatePreview)
246     {
247         var collectionMode = InjectedScript.CollectionMode.OwnProperties | InjectedScript.CollectionMode.NativeGetterProperties;
248         return this._getProperties(objectId, collectionMode, generatePreview);
249     },
250
251     getInternalProperties: function(objectId, generatePreview)
252     {
253         var parsedObjectId = this._parseObjectId(objectId);
254         var object = this._objectForId(parsedObjectId);
255         var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
256
257         if (!this._isDefined(object))
258             return false;
259
260         if (isSymbol(object))
261             return false;
262
263         var descriptors = this._internalPropertyDescriptors(object);
264         if (!descriptors)
265             return [];
266
267         // Go over properties, wrap object values.
268         for (var i = 0; i < descriptors.length; ++i) {
269             var descriptor = descriptors[i];
270             if ("value" in descriptor)
271                 descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
272         }
273
274         return descriptors;
275     },
276
277     getCollectionEntries: function(objectId, objectGroupName, startIndex, numberToFetch)
278     {
279         var parsedObjectId = this._parseObjectId(objectId);
280         var object = this._objectForId(parsedObjectId);
281         var objectGroupName = objectGroupName || this._idToObjectGroupName[parsedObjectId.id];
282
283         if (!this._isDefined(object))
284             return;
285
286         if (typeof object !== "object")
287             return;
288
289         var entries = this._getCollectionEntries(object, InjectedScriptHost.subtype(object), startIndex, numberToFetch);
290         return entries.map(function(entry) {
291             entry.value = injectedScript._wrapObject(entry.value, objectGroupName, false, true);
292             if ("key" in entry)
293                 entry.key = injectedScript._wrapObject(entry.key, objectGroupName, false, true);
294             return entry;
295         });
296     },
297
298     saveResult: function(callArgumentJSON)
299     {
300         this._savedResultIndex = 0;
301
302         try {
303             var callArgument = InjectedScriptHost.evaluate("(" + callArgumentJSON + ")");
304             var value = this._resolveCallArgument(callArgument);
305             this._saveResult(value);
306         } catch (e) {}
307
308         return this._savedResultIndex;
309     },
310
311     getFunctionDetails: function(functionId)
312     {
313         var parsedFunctionId = this._parseObjectId(functionId);
314         var func = this._objectForId(parsedFunctionId);
315         if (typeof func !== "function")
316             return "Cannot resolve function by id.";
317         var details = InjectedScriptHost.functionDetails(func);
318         if (!details)
319             return "Cannot resolve function details.";
320         if ("rawScopes" in details) {
321             var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id];
322             var rawScopes = details.rawScopes;
323             var scopes = [];
324             delete details.rawScopes;
325             for (var i = 0; i < rawScopes.length; i++)
326                 scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName));
327             details.scopeChain = scopes;
328         }
329         return details;
330     },
331
332     releaseObject: function(objectId)
333     {
334         var parsedObjectId = this._parseObjectId(objectId);
335         this._releaseObject(parsedObjectId.id);
336     },
337
338     _releaseObject: function(id)
339     {
340         delete this._idToWrappedObject[id];
341         delete this._idToObjectGroupName[id];
342     },
343
344     evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
345     {
346         return this._evaluateAndWrap(InjectedScriptHost.evaluate, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
347     },
348
349     callFunctionOn: function(objectId, expression, args, returnByValue, generatePreview)
350     {
351         var parsedObjectId = this._parseObjectId(objectId);
352         var object = this._objectForId(parsedObjectId);
353         if (!this._isDefined(object))
354             return "Could not find object with given id";
355
356         if (args) {
357             var resolvedArgs = [];
358             var callArgs = InjectedScriptHost.evaluate(args);
359             for (var i = 0; i < callArgs.length; ++i) {
360                 try {
361                     resolvedArgs[i] = this._resolveCallArgument(callArgs[i]);
362                 } catch (e) {
363                     return String(e);
364                 }
365             }
366         }
367
368         try {
369             var objectGroup = this._idToObjectGroupName[parsedObjectId.id];
370             var func = InjectedScriptHost.evaluate("(" + expression + ")");
371             if (typeof func !== "function")
372                 return "Given expression does not evaluate to a function";
373
374             return {
375                 wasThrown: false,
376                 result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue, generatePreview)
377             };
378         } catch (e) {
379             return this._createThrownValue(e, objectGroup);
380         }
381     },
382
383     _resolveCallArgument: function(callArgumentJSON)
384     {
385         if ("value" in callArgumentJSON)
386             return callArgumentJSON.value;
387
388         var objectId = callArgumentJSON.objectId;
389         if (objectId) {
390             var parsedArgId = this._parseObjectId(objectId);
391             if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId)
392                 throw "Arguments should belong to the same JavaScript world as the target object.";
393
394             var resolvedArg = this._objectForId(parsedArgId);
395             if (!this._isDefined(resolvedArg))
396                 throw "Could not find object with given id";
397
398             return resolvedArg;
399         }
400
401         return undefined;
402     },
403
404     _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
405     {
406         try {
407             this._savedResultIndex = 0;
408
409             var returnObject = {
410                 wasThrown: false,
411                 result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult), objectGroup, returnByValue, generatePreview)
412             };
413
414             if (saveResult && this._savedResultIndex)
415                 returnObject.savedResultIndex = this._savedResultIndex;
416
417             return returnObject;
418         } catch (e) {
419             return this._createThrownValue(e, objectGroup);
420         }
421     },
422
423     _createThrownValue: function(value, objectGroup)
424     {
425         var remoteObject = this._wrapObject(value, objectGroup);
426         try {
427             remoteObject.description = toString(value);
428         } catch (e) {}
429         return {
430             wasThrown: true,
431             result: remoteObject
432         };
433     },
434
435     _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult)
436     {
437         var commandLineAPI = null;
438         if (injectCommandLineAPI) {
439             if (this.CommandLineAPI)
440                 commandLineAPI = new this.CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
441             else
442                 commandLineAPI = new BasicCommandLineAPI;
443         }
444
445         if (isEvalOnCallFrame) {
446             // We can only use this approach if the evaluate function is the true 'eval'. That allows us to use it with
447             // the 'eval' identifier when calling it. Using 'eval' grants access to the local scope of the closure we
448             // create that provides the command line APIs.
449
450             var parameters = [InjectedScriptHost.evaluate, expression];
451             var expressionFunctionBody = "" +
452                 "var global = Function('return this')() || (1, eval)('this');" +
453                 "var __originalEval = global.eval; global.eval = __eval;" +
454                 "try { return eval(__currentExpression); }" +
455                 "finally { global.eval = __originalEval; }";
456
457             if (commandLineAPI) {
458                 // To avoid using a 'with' statement (which fails in strict mode and requires injecting the API object)
459                 // we instead create a closure where we evaluate the expression. The command line APIs are passed as
460                 // parameters to the closure so they are in scope but not injected. This allows the code evaluated in
461                 // the console to stay in strict mode (if is was already set), or to get strict mode by prefixing
462                 // expressions with 'use strict';.
463
464                 var parameterNames = Object.getOwnPropertyNames(commandLineAPI);
465                 for (var i = 0; i < parameterNames.length; ++i)
466                     parameters.push(commandLineAPI[parameterNames[i]]);
467
468                 var expressionFunctionString = "(function(__eval, __currentExpression, " + parameterNames.join(", ") + ") { " + expressionFunctionBody + " })";
469             } else {
470                 // Use a closure in this case too to keep the same behavior of 'var' being captured by the closure instead
471                 // of leaking out into the calling scope.
472                 var expressionFunctionString = "(function(__eval, __currentExpression) { " + expressionFunctionBody + " })";
473             }
474
475             // Bind 'this' to the function expression using another closure instead of Function.prototype.bind. This ensures things will work if the page replaces bind.
476             var boundExpressionFunctionString = "(function(__function, __thisObject) { return function() { return __function.apply(__thisObject, arguments) }; })(" + expressionFunctionString + ", this)";
477             var expressionFunction = evalFunction.call(object, boundExpressionFunctionString);
478             var result = expressionFunction.apply(null, parameters);
479
480             if (objectGroup === "console" && saveResult)
481                 this._saveResult(result);
482
483             return result;
484         }
485
486         // When not evaluating on a call frame we use a 'with' statement to allow var and function statements to leak
487         // into the global scope. This allow them to stick around between evaluations.
488
489         try {
490             if (commandLineAPI) {
491                 if (inspectedGlobalObject.console)
492                     inspectedGlobalObject.console.__commandLineAPI = commandLineAPI;
493                 else
494                     inspectedGlobalObject.__commandLineAPI = commandLineAPI;
495                 expression = "with ((this && (this.console ? this.console.__commandLineAPI : this.__commandLineAPI)) || {}) { " + expression + "\n}";
496             }
497
498             var result = evalFunction.call(inspectedGlobalObject, expression);
499
500             if (objectGroup === "console" && saveResult)
501                 this._saveResult(result);
502
503             return result;
504         } finally {
505             if (commandLineAPI) {
506                 if (inspectedGlobalObject.console)
507                     delete inspectedGlobalObject.console.__commandLineAPI;
508                 else
509                     delete inspectedGlobalObject.__commandLineAPI;
510             }
511         }
512     },
513
514     wrapCallFrames: function(callFrame)
515     {
516         if (!callFrame)
517             return false;
518
519         var result = [];
520         var depth = 0;
521         do {
522             result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
523             callFrame = callFrame.caller;
524         } while (callFrame);
525         return result;
526     },
527
528     evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
529     {
530         var callFrame = this._callFrameForId(topCallFrame, callFrameId);
531         if (!callFrame)
532             return "Could not find call frame with given id";
533         return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
534     },
535
536     _callFrameForId: function(topCallFrame, callFrameId)
537     {
538         var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")");
539         var ordinal = parsedCallFrameId["ordinal"];
540         var callFrame = topCallFrame;
541         while (--ordinal >= 0 && callFrame)
542             callFrame = callFrame.caller;
543         return callFrame;
544     },
545
546     _objectForId: function(objectId)
547     {
548         return this._idToWrappedObject[objectId.id];
549     },
550
551     findObjectById: function(objectId)
552     {
553         var parsedObjectId = this._parseObjectId(objectId);
554         return this._objectForId(parsedObjectId);
555     },
556
557     module: function(name)
558     {
559         return this._modules[name];
560     },
561
562     injectModule: function(name, source, host)
563     {
564         delete this._modules[name];
565
566         var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")");
567         if (typeof moduleFunction !== "function") {
568             if (inspectedGlobalObject.console)
569                 inspectedGlobalObject.console.error("Web Inspector error: A function was expected for module %s evaluation", name);
570             return null;
571         }
572
573         var module = moduleFunction.call(inspectedGlobalObject, InjectedScriptHost, inspectedGlobalObject, injectedScriptId, this, host);
574         this._modules[name] = module;
575         return module;
576     },
577
578     _internalPropertyDescriptors: function(object, completeDescriptor)
579     {
580         var internalProperties = InjectedScriptHost.getInternalProperties(object);
581         if (!internalProperties)
582             return null;
583
584         var descriptors = [];
585         for (var i = 0; i < internalProperties.length; i++) {
586             var property = internalProperties[i];
587             var descriptor = {name: property.name, value: property.value};
588             if (completeDescriptor) {
589                 descriptor.writable = false;
590                 descriptor.configurable = false;
591                 descriptor.enumerable = false;
592                 descriptor.isOwn = true;
593             }
594             descriptors.push(descriptor);
595         }
596         return descriptors;
597     },
598
599     _propertyDescriptors: function(object, collectionMode)
600     {
601         var descriptors = [];
602         var nameProcessed = {};
603         nameProcessed["__proto__"] = null;
604
605         function createFakeValueDescriptor(name, descriptor, isOwnProperty, possibleNativeBindingGetter)
606         {
607             try {
608                 var descriptor = {name: name, value: object[name], writable: descriptor.writable || false, configurable: descriptor.configurable || false, enumerable: descriptor.enumerable || false};
609                 if (possibleNativeBindingGetter)
610                     descriptor.nativeGetter = true;
611                 return descriptor;
612             } catch (e) {
613                 var errorDescriptor = {name: name, value: e, wasThrown: true};
614                 if (isOwnProperty)
615                     errorDescriptor.isOwn = true;
616                 return errorDescriptor;
617             }
618         }
619
620         function processDescriptor(descriptor, isOwnProperty, possibleNativeBindingGetter)
621         {
622             // All properties.
623             if (collectionMode & InjectedScript.CollectionMode.AllProperties) {
624                 descriptors.push(descriptor);
625                 return;
626             }
627
628             // Own properties.
629             if (collectionMode & InjectedScript.CollectionMode.OwnProperties && isOwnProperty) {
630                 descriptors.push(descriptor);
631                 return;
632             }
633
634             // Native Getter properties.
635             if (collectionMode & InjectedScript.CollectionMode.NativeGetterProperties) {
636                 // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete
637                 // if (descriptor.hasOwnProperty("get") && descriptor.get && isNativeFunction(descriptor.get)) { ... }
638
639                 if (possibleNativeBindingGetter) {
640                     // Possible getter property in the prototype chain.
641                     descriptors.push(descriptor);
642                     return;
643                 }
644             }
645         }
646
647         function processPropertyNames(o, names, isOwnProperty)
648         {
649             for (var i = 0; i < names.length; ++i) {
650                 var name = names[i];
651                 if (nameProcessed[name] || name === "__proto__")
652                     continue;
653
654                 nameProcessed[name] = true;
655
656                 var descriptor = Object.getOwnPropertyDescriptor(o, name);
657                 if (!descriptor) {
658                     // FIXME: Bad descriptor. Can we get here?
659                     // Fall back to very restrictive settings.
660                     var fakeDescriptor = createFakeValueDescriptor(name, {writable: false, configurable: false, enumerable: false}, isOwnProperty);
661                     processDescriptor(fakeDescriptor, isOwnProperty);
662                     continue;
663                 }
664
665                 if (descriptor.hasOwnProperty("get") && descriptor.hasOwnProperty("set") && !descriptor.get && !descriptor.set) {
666                     // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete
667                     // Developers may create such a descriptors, so we should be resilient:
668                     // var x = {}; Object.defineProperty(x, "p", {get:undefined}); Object.getOwnPropertyDescriptor(x, "p")
669                     var fakeDescriptor = createFakeValueDescriptor(name, descriptor, isOwnProperty, true);
670                     processDescriptor(fakeDescriptor, isOwnProperty, true);
671                     continue;
672                 }
673
674                 descriptor.name = name;
675                 if (isOwnProperty)
676                     descriptor.isOwn = true;
677                 processDescriptor(descriptor, isOwnProperty);
678             }
679         }
680
681         // Iterate prototype chain.
682         for (var o = object; this._isDefined(o); o = o.__proto__) {
683             var isOwnProperty = o === object;
684             processPropertyNames(o, Object.getOwnPropertyNames(o), isOwnProperty);
685             if (collectionMode === InjectedScript.CollectionMode.OwnProperties)
686                 break;
687         }
688
689         // Always include __proto__ at the end.
690         try {
691             if (object.__proto__)
692                 descriptors.push({name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true});
693         } catch (e) {}
694
695         return descriptors;
696     },
697
698     _isDefined: function(object)
699     {
700         return !!object || this._isHTMLAllCollection(object);
701     },
702
703     _isHTMLAllCollection: function(object)
704     {
705         // document.all is reported as undefined, but we still want to process it.
706         return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object);
707     },
708
709     _subtype: function(obj)
710     {
711         if (obj === null)
712             return "null";
713
714         if (this.isPrimitiveValue(obj) || isSymbol(obj))
715             return null;
716
717         if (this._isHTMLAllCollection(obj))
718             return "array";
719
720         var preciseType = InjectedScriptHost.subtype(obj);
721         if (preciseType)
722             return preciseType;
723
724         // FireBug's array detection.
725         try {
726             if (typeof obj.splice === "function" && isFinite(obj.length))
727                 return "array";
728             if (Object.prototype.toString.call(obj) === "[object Arguments]" && isFinite(obj.length)) // arguments.
729                 return "array";
730         } catch (e) {
731         }
732
733         return null;
734     },
735
736     _describe: function(obj)
737     {
738         if (this.isPrimitiveValue(obj))
739             return null;
740
741         if (isSymbol(obj))
742             return toString(obj);
743
744         var subtype = this._subtype(obj);
745
746         if (subtype === "regexp")
747             return toString(obj);
748
749         if (subtype === "date")
750             return toString(obj);
751
752         if (subtype === "error")
753             return toString(obj);
754
755         if (subtype === "node") {
756             var description = obj.nodeName.toLowerCase();
757             switch (obj.nodeType) {
758             case 1 /* Node.ELEMENT_NODE */:
759                 description += obj.id ? "#" + obj.id : "";
760                 var className = obj.className;
761                 description += (className && typeof className === "string") ? "." + className.trim().replace(/\s+/g, ".") : "";
762                 break;
763             case 10 /*Node.DOCUMENT_TYPE_NODE */:
764                 description = "<!DOCTYPE " + description + ">";
765                 break;
766             }
767             return description;
768         }
769
770         var className = InjectedScriptHost.internalConstructorName(obj);
771         if (subtype === "array") {
772             if (typeof obj.length === "number")
773                 className += "[" + obj.length + "]";
774             return className;
775         }
776
777         // NodeList in JSC is a function, check for array prior to this.
778         if (typeof obj === "function")
779             return toString(obj);
780
781         // If Object, try for a better name from the constructor.
782         if (className === "Object") {
783             var constructorName = obj.constructor && obj.constructor.name;
784             if (constructorName)
785                 return constructorName;
786         }
787
788         return className;
789     },
790
791     _getSetEntries: function(object, skip, numberToFetch)
792     {
793         var entries = [];
794
795         for (var value of object) {
796             if (skip > 0) {
797                 skip--;
798                 continue;
799             }
800
801             entries.push({value: value});
802
803             if (numberToFetch && entries.length === numberToFetch)
804                 break;
805         }
806
807         return entries;
808     },
809
810     _getMapEntries: function(object, skip, numberToFetch)
811     {
812         var entries = [];
813
814         for (var [key, value] of object) {
815             if (skip > 0) {
816                 skip--;
817                 continue;
818             }
819
820             entries.push({key: key, value: value});
821
822             if (numberToFetch && entries.length === numberToFetch)
823                 break;
824         }
825
826         return entries;
827     },
828
829     _getWeakMapEntries: function(object, numberToFetch)
830     {
831         return InjectedScriptHost.weakMapEntries(object, numberToFetch);
832     },
833
834     _getCollectionEntries: function(object, subtype, startIndex, numberToFetch)
835     {
836         if (subtype === "set")
837             return this._getSetEntries(object, startIndex, numberToFetch);
838         if (subtype === "map")
839             return this._getMapEntries(object, startIndex, numberToFetch);
840         if (subtype === "weakmap")
841             return this._getWeakMapEntries(object, numberToFetch);
842
843         throw "unexpected type";
844     },
845
846     _saveResult: function(result)
847     {
848         this._lastResult = result;
849
850         if (result === undefined || result === null)
851             return;
852
853         var existingIndex = this._savedResults.indexOf(result);
854         if (existingIndex !== -1) {
855             this._savedResultIndex = existingIndex;
856             return;
857         }
858
859         this._savedResultIndex = this._nextSavedResultIndex;
860         this._savedResults[this._nextSavedResultIndex++] = result;
861
862         // $n is limited from $1-$99. $0 is special.
863         if (this._nextSavedResultIndex >= 100)
864             this._nextSavedResultIndex = 1;
865     },
866
867     _savedResult: function(index)
868     {
869         return this._savedResults[index];
870     }
871 }
872
873 var injectedScript = new InjectedScript;
874
875
876 InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames)
877 {
878     this.type = typeof object;
879
880     if (this.type === "undefined" && injectedScript._isHTMLAllCollection(object))
881         this.type = "object";
882
883     if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
884         // We don't send undefined values over JSON.
885         if (this.type !== "undefined")
886             this.value = object;
887
888         // Null object is object with 'null' subtype.
889         if (object === null)
890             this.subtype = "null";
891
892         // Provide user-friendly number values.
893         if (this.type === "number")
894             this.description = object + "";
895         return;
896     }
897
898     this.objectId = injectedScript._bind(object, objectGroupName);
899
900     var subtype = injectedScript._subtype(object);
901     if (subtype)
902         this.subtype = subtype;
903
904     this.className = InjectedScriptHost.internalConstructorName(object);
905     this.description = injectedScript._describe(object);
906
907     if (generatePreview && this.type === "object")
908         this.preview = this._generatePreview(object, undefined, columnNames);
909 }
910
911 InjectedScript.RemoteObject.prototype = {
912     _emptyPreview: function()
913     {
914         var preview = {
915             type: this.type,
916             description: this.description || toString(this.value),
917             lossless: true,
918         };
919
920         if (this.subtype) {
921             preview.subtype = this.subtype;
922             if (this.subtype !== "null") {
923                 preview.overflow = false;
924                 preview.properties = [];
925             }
926         }
927
928         return preview;
929     },
930
931     _createObjectPreviewForValue: function(value)
932     {
933         var remoteObject = new InjectedScript.RemoteObject(value, undefined, false, true, undefined);
934         if (remoteObject.objectId)
935             injectedScript.releaseObject(remoteObject.objectId);
936
937         return remoteObject.preview || remoteObject._emptyPreview();
938     },
939
940     _generatePreview: function(object, firstLevelKeys, secondLevelKeys)
941     {
942         var preview = this._emptyPreview();
943
944         // Primitives just have a value.
945         if (this.type !== "object")
946             return;
947
948         var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys;
949         var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
950
951         var propertiesThreshold = {
952             properties: isTableRowsRequest ? 1000 : Math.max(5, firstLevelKeysCount),
953             indexes: isTableRowsRequest ? 1000 : Math.max(100, firstLevelKeysCount)
954         };
955
956         try {
957             // Maps and Sets have entries.
958             if (this.subtype === "map" || this.subtype === "set" || this.subtype === "weakmap")
959                 this._appendEntryPreviews(object, preview);
960
961             preview.properties = [];
962
963             // Internal Properties.
964             var internalPropertyDescriptors = injectedScript._internalPropertyDescriptors(object, true);
965             if (internalPropertyDescriptors) {
966                 this._appendPropertyPreviews(preview, internalPropertyDescriptors, true, propertiesThreshold, firstLevelKeys, secondLevelKeys);
967                 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
968                     return preview;
969             }
970
971             // Properties.
972             var descriptors = injectedScript._propertyDescriptors(object, InjectedScript.CollectionMode.AllProperties);
973             this._appendPropertyPreviews(preview, descriptors, false, propertiesThreshold, firstLevelKeys, secondLevelKeys);
974             if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
975                 return preview;
976
977             // FIXME: Iterator entries.
978         } catch (e) {
979             preview.lossless = false;
980         }
981
982         return preview;
983     },
984
985     _appendPropertyPreviews: function(preview, descriptors, internal, propertiesThreshold, firstLevelKeys, secondLevelKeys)
986     {
987         for (var descriptor of descriptors) {
988             // Seen enough.
989             if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
990                 break;
991
992             // Error in descriptor.
993             if (descriptor.wasThrown) {
994                 preview.lossless = false;
995                 continue;
996             }
997
998             // Do not show "__proto__" in preview.
999             var name = descriptor.name;
1000             if (name === "__proto__")
1001                 continue;
1002
1003             // Do not show "length" on array like objects in preview.
1004             if (this.subtype === "array" && name === "length")
1005                 continue;
1006
1007             // Do not show non-enumerable non-own properties. Special case to allow array indexes that may be on the prototype.
1008             if (!descriptor.enumerable && !descriptor.isOwn && !(this.subtype === "array" && isUInt32(name)))
1009                 continue;
1010
1011             // If we have a filter, only show properties in the filter.
1012             if (firstLevelKeys && firstLevelKeys.indexOf(name) === -1)
1013                 continue;
1014
1015             // Getter/setter.
1016             if (!("value" in descriptor)) {
1017                 preview.lossless = false;
1018                 this._appendPropertyPreview(preview, internal, {name: name, type: "accessor"}, propertiesThreshold);
1019                 continue;
1020             }
1021
1022             // Null value.
1023             var value = descriptor.value;
1024             if (value === null) {
1025                 this._appendPropertyPreview(preview, internal, {name: name, type: "object", subtype: "null", value: "null"}, propertiesThreshold);
1026                 continue;
1027             }
1028
1029             // Ignore non-enumerable functions.
1030             var type = typeof value;
1031             if (!descriptor.enumerable && type === "function")
1032                 continue;
1033
1034             // Fix type of document.all.
1035             if (type === "undefined" && injectedScript._isHTMLAllCollection(value))
1036                 type = "object";
1037
1038             // Primitive.
1039             const maxLength = 100;
1040             if (InjectedScript.primitiveTypes[type]) {
1041                 if (type === "string" && value.length > maxLength) {
1042                     value = this._abbreviateString(value, maxLength, true);
1043                     preview.lossless = false;
1044                 }
1045                 this._appendPropertyPreview(preview, internal, {name: name, type: type, value: toString(value)}, propertiesThreshold);
1046                 continue;
1047             }
1048
1049             // Symbol.
1050             if (isSymbol(value)) {
1051                 var symbolString = toString(value);
1052                 if (symbolString.length > maxLength) {
1053                     symbolString = this._abbreviateString(symbolString, maxLength, true);
1054                     preview.lossless = false;
1055                 }
1056                 this._appendPropertyPreview(preview, internal, {name: name, type: type, value: symbolString}, propertiesThreshold);
1057                 return;
1058             }
1059
1060             // Object.
1061             var property = {name: name, type: type};
1062             var subtype = injectedScript._subtype(value);
1063             if (subtype)
1064                 property.subtype = subtype;
1065
1066             // Second level.
1067             if (secondLevelKeys === null || secondLevelKeys) {
1068                 var subPreview = this._generatePreview(value, secondLevelKeys || undefined, undefined);
1069                 property.valuePreview = subPreview;
1070                 if (!subPreview.lossless)
1071                     preview.lossless = false;
1072                 if (subPreview.overflow)
1073                     preview.overflow = true;
1074             } else {
1075                 var description = "";
1076                 if (type !== "function")
1077                     description = this._abbreviateString(injectedScript._describe(value), maxLength, subtype === "regexp");
1078                 property.value = description;
1079                 preview.lossless = false;
1080             }
1081
1082             this._appendPropertyPreview(preview, internal, property, propertiesThreshold);
1083         }
1084     },
1085
1086     _appendPropertyPreview: function(preview, internal, property, propertiesThreshold)
1087     {
1088         if (toString(property.name >>> 0) === property.name)
1089             propertiesThreshold.indexes--;
1090         else
1091             propertiesThreshold.properties--;
1092
1093         if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) {
1094             preview.overflow = true;
1095             preview.lossless = false;
1096             return;
1097         }
1098
1099         if (internal)
1100             property.internal = true;
1101
1102         preview.properties.push(property);
1103     },
1104
1105     _appendEntryPreviews: function(object, preview)
1106     {
1107         // Fetch 6, but only return 5, so we can tell if we overflowed.
1108         var entries = injectedScript._getCollectionEntries(object, this.subtype, 0, 6);
1109         if (!entries)
1110             return;
1111
1112         if (entries.length > 5) {
1113             entries.pop();
1114             preview.overflow = true;
1115             preview.lossless = false;
1116         }
1117
1118         preview.entries = entries.map(function(entry) {
1119             entry.value = this._createObjectPreviewForValue(entry.value);
1120             if ("key" in entry)
1121                 entry.key = this._createObjectPreviewForValue(entry.key);
1122             return entry;
1123         }, this);
1124     },
1125
1126     _abbreviateString: function(string, maxLength, middle)
1127     {
1128         if (string.length <= maxLength)
1129             return string;
1130
1131         if (middle) {
1132             var leftHalf = maxLength >> 1;
1133             var rightHalf = maxLength - leftHalf - 1;
1134             return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf);
1135         }
1136
1137         return string.substr(0, maxLength) + "\u2026";
1138     }
1139 }
1140
1141 InjectedScript.CallFrameProxy = function(ordinal, callFrame)
1142 {
1143     this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}";
1144     this.functionName = (callFrame.type === "function" ? callFrame.functionName : "");
1145     this.location = {scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column};
1146     this.scopeChain = this._wrapScopeChain(callFrame);
1147     this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace");
1148 }
1149
1150 InjectedScript.CallFrameProxy.prototype = {
1151     _wrapScopeChain: function(callFrame)
1152     {
1153         var scopeChain = callFrame.scopeChain;
1154         var scopeChainProxy = [];
1155         for (var i = 0; i < scopeChain.length; i++)
1156             scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace");
1157         return scopeChainProxy;
1158     }
1159 }
1160
1161 InjectedScript.CallFrameProxy._scopeTypeNames = {
1162     0: "global", // GLOBAL_SCOPE
1163     1: "local", // LOCAL_SCOPE
1164     2: "with", // WITH_SCOPE
1165     3: "closure", // CLOSURE_SCOPE
1166     4: "catch", // CATCH_SCOPE
1167     5: "functionName", // FUNCTION_NAME_SCOPE
1168 }
1169
1170 InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId)
1171 {
1172     return {
1173         object: injectedScript._wrapObject(scopeObject, groupId),
1174         type: InjectedScript.CallFrameProxy._scopeTypeNames[scopeTypeCode]
1175     };
1176 }
1177
1178
1179 function slice(array, index)
1180 {
1181     var result = [];
1182     for (var i = index || 0; i < array.length; ++i)
1183         result.push(array[i]);
1184     return result;
1185 }
1186
1187 function bind(func, thisObject, var_args)
1188 {
1189     var args = slice(arguments, 2);
1190     return function(var_args) {
1191         return func.apply(thisObject, args.concat(slice(arguments)));
1192     }
1193 }
1194
1195 function BasicCommandLineAPI()
1196 {
1197     this.$_ = injectedScript._lastResult;
1198     this.$exception = injectedScript._exceptionValue;
1199
1200     // $1-$99
1201     for (var i = 1; i <= injectedScript._savedResults.length; ++i) {
1202         var member = "$" + i;
1203         if (member in inspectedGlobalObject)
1204             continue;
1205         this.__defineGetter__("$" + i, bind(injectedScript._savedResult, injectedScript, i));
1206     }
1207 }
1208
1209 return injectedScript;
1210 })