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