Web Inspector: ES6: Provide a better view for Classes in the console
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Protocol / RemoteObject.js
1 /*
2  * Copyright (C) 2009 Google Inc. All rights reserved.
3  * Copyright (C) 2015 Apple 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 are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 WebInspector.RemoteObject = class RemoteObject
33 {
34     constructor(objectId, type, subtype, value, description, size, classPrototype, preview)
35     {
36         console.assert(type);
37         console.assert(!preview || preview instanceof WebInspector.ObjectPreview);
38
39         this._type = type;
40         this._subtype = subtype;
41
42         if (objectId) {
43             // Object, Function, or Symbol.
44             console.assert(!subtype || typeof subtype === "string");
45             console.assert(!description || typeof description === "string");
46             console.assert(!value);
47
48             this._objectId = objectId;
49             this._description = description;
50             this._hasChildren = type !== "symbol";
51             this._size = size;
52             this._classPrototype = classPrototype;
53             this._preview = preview;
54
55             if (subtype === "class")
56                 this._description = "class " + this._description;
57         } else {
58             // Primitive or null.
59             console.assert(type !== "object" || value === null);
60             console.assert(!preview);
61
62             this._description = description || (value + "");
63             this._hasChildren = false;
64             this._value = value;
65         }
66     }
67
68     // Static
69
70     static fromPrimitiveValue(value)
71     {
72         return new WebInspector.RemoteObject(undefined, typeof value, undefined, value, undefined, undefined, undefined);
73     }
74
75     static fromPayload(payload)
76     {
77         console.assert(typeof payload === "object", "Remote object payload should only be an object");
78
79         if (payload.subtype === "array") {
80             // COMPATIBILITY (iOS 8): Runtime.RemoteObject did not have size property,
81             // instead it was tacked onto the end of the description, like "Array[#]".
82             var match = payload.description.match(/\[(\d+)\]$/);
83             if (match) {
84                 payload.size = parseInt(match[1]);
85                 payload.description = payload.description.replace(/\[\d+\]$/, "");
86             }
87         }
88
89         if (payload.classPrototype)
90             payload.classPrototype = WebInspector.RemoteObject.fromPayload(payload.classPrototype);
91
92         if (payload.preview) {
93             // COMPATIBILITY (iOS 8): iOS 7 and 8 did not have type/subtype/description on
94             // Runtime.ObjectPreview. Copy them over from the RemoteObject.
95             if (!payload.preview.type) {
96                 payload.preview.type = payload.type;
97                 payload.preview.subtype = payload.subtype;
98                 payload.preview.description = payload.description;
99                 payload.preview.size = payload.size;
100             }
101
102             payload.preview = WebInspector.ObjectPreview.fromPayload(payload.preview);
103         }
104
105         return new WebInspector.RemoteObject(payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.size, payload.classPrototype, payload.preview);
106     }
107
108     static createCallArgument(valueOrObject)
109     {
110         if (valueOrObject instanceof WebInspector.RemoteObject) {
111             if (valueOrObject.objectId)
112                 return {objectId: valueOrObject.objectId};
113             return {value: valueOrObject.value};
114         }
115
116         return {value: valueOrObject};
117     }
118
119     static resolveNode(node, objectGroup, callback)
120     {
121         DOMAgent.resolveNode(node.id, objectGroup, function(error, object) {
122             if (!callback)
123                 return;
124
125             if (error || !object)
126                 callback(null);
127             else
128                 callback(WebInspector.RemoteObject.fromPayload(object));
129         });
130     }
131
132     static type(remoteObject)
133     {
134         if (remoteObject === null)
135             return "null";
136
137         var type = typeof remoteObject;
138         if (type !== "object" && type !== "function")
139             return type;
140
141         return remoteObject.type;
142     }
143
144     // Public
145
146     get objectId()
147     {
148         return this._objectId;
149     }
150
151     get type()
152     {
153         return this._type;
154     }
155
156     get subtype()
157     {
158         return this._subtype;
159     }
160
161     get description()
162     {
163         return this._description;
164     }
165
166     get hasChildren()
167     {
168         return this._hasChildren;
169     }
170
171     get value()
172     {
173         return this._value;
174     }
175
176     get size()
177     {
178         return this._size || 0;
179     }
180
181     get classPrototype()
182     {
183         return this._classPrototype;
184     }
185
186     get preview()
187     {
188         return this._preview;
189     }
190
191     hasSize()
192     {
193         return this.isArray() || this.isCollectionType();
194     }
195
196     hasValue()
197     {
198         return "_value" in this;
199     }
200
201     getOwnPropertyDescriptors(callback)
202     {
203         this._getPropertyDescriptors(true, callback);
204     }
205
206     getAllPropertyDescriptors(callback)
207     {
208         this._getPropertyDescriptors(false, callback);
209     }
210
211     getDisplayablePropertyDescriptors(callback)
212     {
213         if (!this._objectId || this._isSymbol()) {
214             callback([]);
215             return;
216         }
217
218         // COMPATIBILITY (iOS 8): RuntimeAgent.getDisplayableProperties did not exist.
219         // Here we do our best to reimplement it by getting all properties and reducing them down.
220         if (!RuntimeAgent.getDisplayableProperties) {
221             RuntimeAgent.getProperties(this._objectId, function(error, allProperties) {
222                 var ownOrGetterPropertiesList = [];
223                 if (allProperties) {
224                     for (var property of allProperties) {
225                         if (property.isOwn || property.name === "__proto__") {
226                             // Own property or getter property in prototype chain.
227                             ownOrGetterPropertiesList.push(property);
228                         } else if (property.value && property.name !== property.name.toUpperCase()) {
229                             var type = property.value.type;
230                             if (type && type !== "function" && property.name !== "constructor") {
231                                 // Possible native binding getter property converted to a value. Also, no CONSTANT name style and not "constructor".
232                                 // There is no way of knowing if this is native or not, so just go with it.
233                                 ownOrGetterPropertiesList.push(property);
234                             }
235                         }
236                     }
237                 }
238
239                 this._getPropertyDescriptorsResolver(callback, error, ownOrGetterPropertiesList);
240             }.bind(this));
241             return;
242         }
243
244         RuntimeAgent.getDisplayableProperties(this._objectId, true, this._getPropertyDescriptorsResolver.bind(this, callback));
245     }
246
247     // FIXME: Phase out these deprecated functions. They return DeprecatedRemoteObjectProperty instead of PropertyDescriptors.
248     deprecatedGetOwnProperties(callback)
249     {
250         this._deprecatedGetProperties(true, callback);
251     }
252
253     deprecatedGetAllProperties(callback)
254     {
255         this._deprecatedGetProperties(false, callback);
256     }
257
258     deprecatedGetDisplayableProperties(callback)
259     {
260         if (!this._objectId || this._isSymbol()) {
261             callback([]);
262             return;
263         }
264
265         // COMPATIBILITY (iOS 8): RuntimeAgent.getProperties did not support ownerAndGetterProperties.
266         // Here we do our best to reimplement it by getting all properties and reducing them down.
267         if (!RuntimeAgent.getDisplayableProperties) {
268             RuntimeAgent.getProperties(this._objectId, function(error, allProperties) {
269                 var ownOrGetterPropertiesList = [];
270                 if (allProperties) {
271                     for (var property of allProperties) {
272                         if (property.isOwn || property.get || property.name === "__proto__") {
273                             // Own property or getter property in prototype chain.
274                             ownOrGetterPropertiesList.push(property);
275                         } else if (property.value && property.name !== property.name.toUpperCase()) {
276                             var type = property.value.type;
277                             if (type && type !== "function" && property.name !== "constructor") {
278                                 // Possible native binding getter property converted to a value. Also, no CONSTANT name style and not "constructor".
279                                 ownOrGetterPropertiesList.push(property);
280                             }
281                         }
282                     }
283                 }
284
285                 this._deprecatedGetPropertiesResolver(callback, error, ownOrGetterPropertiesList);
286             }.bind(this));
287             return;
288         }
289
290         RuntimeAgent.getDisplayableProperties(this._objectId, this._deprecatedGetPropertiesResolver.bind(this, callback));
291     }
292
293     setPropertyValue(name, value, callback)
294     {
295         if (!this._objectId || this._isSymbol()) {
296             callback("Can't set a property of non-object.");
297             return;
298         }
299
300         RuntimeAgent.evaluate.invoke({expression:value, doNotPauseOnExceptionsAndMuteConsole:true}, evaluatedCallback.bind(this));
301
302         function evaluatedCallback(error, result, wasThrown)
303         {
304             if (error || wasThrown) {
305                 callback(error || result.description);
306                 return;
307             }
308
309             function setPropertyValue(propertyName, propertyValue)
310             {
311                 this[propertyName] = propertyValue;
312             }
313
314             delete result.description; // Optimize on traffic.
315
316             RuntimeAgent.callFunctionOn(this._objectId, setPropertyValue.toString(), [{value:name}, result], true, undefined, propertySetCallback.bind(this));
317
318             if (result._objectId)
319                 RuntimeAgent.releaseObject(result._objectId);
320         }
321
322         function propertySetCallback(error, result, wasThrown)
323         {
324             if (error || wasThrown) {
325                 callback(error || result.description);
326                 return;
327             }
328
329             callback();
330         }
331     }
332
333     isArray()
334     {
335         return this._subtype === "array";
336     }
337
338     isClass()
339     {
340         return this._subtype === "class";
341     }
342
343     isCollectionType()
344     {
345         return this._subtype === "map" || this._subtype === "set" || this._subtype === "weakmap";
346     }
347
348     isWeakCollection()
349     {
350         return this._subtype === "weakmap";
351     }
352
353     getCollectionEntries(start, numberToFetch, callback)
354     {
355         start = typeof start === "number" ? start : 0;
356         numberToFetch = typeof numberToFetch === "number" ? numberToFetch : 100;
357
358         console.assert(start >= 0);
359         console.assert(numberToFetch >= 0);
360         console.assert(this.isCollectionType());
361
362         // WeakMaps are not ordered. We should never send a non-zero start.
363         console.assert((this._subtype === "weakmap" && start === 0) || this._subtype !== "weakmap");
364
365         var objectGroup = this.isWeakCollection() ? this._weakCollectionObjectGroup() : "";
366
367         RuntimeAgent.getCollectionEntries(this._objectId, objectGroup, start, numberToFetch, function(error, entries) {
368             entries = entries.map(function(entry) { return WebInspector.CollectionEntry.fromPayload(entry); });
369             callback(entries);
370         });
371     }
372
373     releaseWeakCollectionEntries()
374     {
375         console.assert(this.isWeakCollection());
376
377         RuntimeAgent.releaseObjectGroup(this._weakCollectionObjectGroup());
378     }
379
380     pushNodeToFrontend(callback)
381     {
382         if (this._objectId)
383             WebInspector.domTreeManager.pushNodeToFrontend(this._objectId, callback);
384         else
385             callback(0);
386     }
387
388     callFunction(functionDeclaration, args, generatePreview, callback)
389     {
390         function mycallback(error, result, wasThrown)
391         {
392             result = result ? WebInspector.RemoteObject.fromPayload(result) : null;
393             callback(error, result, wasThrown);
394         }
395
396         if (args)
397             args = args.map(WebInspector.RemoteObject.createCallArgument);
398
399         RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, undefined, generatePreview, mycallback);
400     }
401
402     callFunctionJSON(functionDeclaration, args, callback)
403     {
404         function mycallback(error, result, wasThrown)
405         {
406             callback((error || wasThrown) ? null : result.value);
407         }
408
409         RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, true, mycallback);
410     }
411     
412     invokeGetter(getterRemoteObject, callback)
413     {
414         console.assert(getterRemoteObject instanceof WebInspector.RemoteObject);
415
416         function backendInvokeGetter(getter)
417         {
418             return getter ? getter.call(this) : undefined;
419         }
420
421         this.callFunction(backendInvokeGetter, [getterRemoteObject], true, callback);
422     }
423
424     getOwnPropertyDescriptor(propertyName, callback)
425     {
426         function backendGetOwnPropertyDescriptor(propertyName)
427         {
428             return this[propertyName];
429         }
430
431         function wrappedCallback(error, result, wasThrown)
432         {
433             if (error || wasThrown || !(result instanceof WebInspector.RemoteObject)) {
434                 callback(null);
435                 return;
436             }
437
438             var fakeDescriptor = {name: propertyName, value: result, writable: true, configurable: true, enumerable: false};
439             var fakePropertyDescriptor = new WebInspector.PropertyDescriptor(fakeDescriptor, true, false, false, false);
440             callback(fakePropertyDescriptor);
441         }
442
443         // FIXME: Implement a real RuntimeAgent.getOwnPropertyDescriptor?
444         this.callFunction(backendGetOwnPropertyDescriptor, [propertyName], false, wrappedCallback);
445     }
446
447     release()
448     {
449         if (this._objectId)
450             RuntimeAgent.releaseObject(this._objectId);
451     }
452
453     arrayLength()
454     {
455         if (this._subtype !== "array")
456             return 0;
457
458         var matches = this._description.match(/\[([0-9]+)\]/);
459         if (!matches)
460             return 0;
461
462         return parseInt(matches[1], 10);
463     }
464
465     asCallArgument()
466     {
467         return WebInspector.RemoteObject.createCallArgument(this);
468     }
469
470     // Private
471
472     _isSymbol()
473     {
474         return this._type === "symbol";
475     }
476
477     _weakCollectionObjectGroup()
478     {
479         return JSON.stringify(this._objectId) + "-WeakMap";
480     }
481
482     _getPropertyDescriptors(ownProperties, callback)
483     {
484         if (!this._objectId || this._isSymbol()) {
485             callback([]);
486             return;
487         }
488
489         RuntimeAgent.getProperties(this._objectId, ownProperties, true, this._getPropertyDescriptorsResolver.bind(this, callback));
490     }
491
492     _getPropertyDescriptorsResolver(callback, error, properties, internalProperties)
493     {
494         if (error) {
495             callback(null);
496             return;
497         }
498
499         var descriptors = properties.map(function(payload) {
500             return WebInspector.PropertyDescriptor.fromPayload(payload);
501         });
502
503         if (internalProperties) {
504             descriptors = descriptors.concat(internalProperties.map(function(payload) {
505                 return WebInspector.PropertyDescriptor.fromPayload(payload, true);
506             }));
507         }
508
509         callback(descriptors);
510     }
511
512     // FIXME: Phase out these deprecated functions. They return DeprecatedRemoteObjectProperty instead of PropertyDescriptors.
513     _deprecatedGetProperties(ownProperties, callback)
514     {
515         if (!this._objectId || this._isSymbol()) {
516             callback([]);
517             return;
518         }
519
520         RuntimeAgent.getProperties(this._objectId, ownProperties, this._deprecatedGetPropertiesResolver.bind(this, callback));
521     }
522
523     _deprecatedGetPropertiesResolver(callback, error, properties, internalProperties)
524     {
525         if (error) {
526             callback(null);
527             return;
528         }
529
530         if (internalProperties) {
531             properties = properties.concat(internalProperties.map(function(descriptor) {
532                 descriptor.writable = false;
533                 descriptor.configurable = false;
534                 descriptor.enumerable = false;
535                 descriptor.isOwn = true;
536                 return descriptor;
537             }));
538         }
539
540         var result = [];
541         for (var i = 0; properties && i < properties.length; ++i) {
542             var property = properties[i];
543             if (property.get || property.set) {
544                 if (property.get)
545                     result.push(new WebInspector.DeprecatedRemoteObjectProperty("get " + property.name, WebInspector.RemoteObject.fromPayload(property.get), property));
546                 if (property.set)
547                     result.push(new WebInspector.DeprecatedRemoteObjectProperty("set " + property.name, WebInspector.RemoteObject.fromPayload(property.set), property));
548             } else
549                 result.push(new WebInspector.DeprecatedRemoteObjectProperty(property.name, WebInspector.RemoteObject.fromPayload(property.value), property));
550         }
551
552         callback(result);
553     }
554 };
555
556 // FIXME: Phase out this deprecated class.
557 WebInspector.DeprecatedRemoteObjectProperty = class DeprecatedRemoteObjectProperty
558 {
559     constructor(name, value, descriptor)
560     {
561         this.name = name;
562         this.value = value;
563         this.enumerable = descriptor ? !!descriptor.enumerable : true;
564         this.writable = descriptor ? !!descriptor.writable : true;
565         if (descriptor && descriptor.wasThrown)
566             this.wasThrown = true;
567     }
568
569     // Static
570
571     fromPrimitiveValue(name, value)
572     {
573         return new WebInspector.DeprecatedRemoteObjectProperty(name, WebInspector.RemoteObject.fromPrimitiveValue(value));
574     }
575 };