Web Inspector: Create Separate Model and View Objects for RemoteObjects / ObjectPrevi...
[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, 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._preview = preview;
51     } else {
52         // Primitive or null.
53         console.assert(type !== "object" || value === null);
54         console.assert(!preview);
55
56         this._description = description || (value + "");
57         this._hasChildren = false;
58         this._value = value;
59     }
60 };
61
62 WebInspector.RemoteObject.fromPrimitiveValue = function(value)
63 {
64     return new WebInspector.RemoteObject(undefined, typeof value, undefined, value);
65 };
66
67 WebInspector.RemoteObject.fromPayload = function(payload)
68 {
69     console.assert(typeof payload === "object", "Remote object payload should only be an object");
70
71     if (payload.preview) {
72         // COMPATIBILITY (iOS 8): iOS 7 and 8 did not have type/subtype/description on
73         // Runtime.ObjectPreview. Copy them over from the RemoteObject.
74         if (!payload.preview.type) {
75             payload.preview.type = obj.type;
76             payload.preview.subtype = obj.subtype;
77             payload.preview.description = obj.description;
78         }
79         payload.preview = WebInspector.ObjectPreview.fromPayload(payload.preview);
80     }
81
82     return new WebInspector.RemoteObject(payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
83 };
84
85 WebInspector.RemoteObject.resolveNode = function(node, objectGroup, callback)
86 {
87     DOMAgent.resolveNode(node.id, objectGroup, function(error, object) {
88         if (!callback)
89             return;
90
91         if (error || !object)
92             callback(null);
93         else
94             callback(WebInspector.RemoteObject.fromPayload(object));
95     });
96 };
97
98 WebInspector.RemoteObject.type = function(remoteObject)
99 {
100     if (remoteObject === null)
101         return "null";
102
103     var type = typeof remoteObject;
104     if (type !== "object" && type !== "function")
105         return type;
106
107     return remoteObject.type;
108 };
109
110 WebInspector.RemoteObject.prototype = {
111     constructor: WebInspector.RemoteObject,
112
113     get objectId()
114     {
115         return this._objectId;
116     },
117
118     get type()
119     {
120         return this._type;
121     },
122
123     get subtype()
124     {
125         return this._subtype;
126     },
127
128     get description()
129     {
130         return this._description;
131     },
132
133     get hasChildren()
134     {
135         return this._hasChildren;
136     },
137
138     get value()
139     {
140         return this._value;
141     },
142
143     get preview()
144     {
145         return this._preview;
146     },
147
148     getOwnPropertyDescriptors: function(callback)
149     {
150         this._getPropertyDescriptors(true, false, callback);
151     },
152
153     getOwnAndGetterPropertyDescriptors: function(callback)
154     {
155         this._getPropertyDescriptors(false, true, callback);
156     },
157
158     getAllPropertyDescriptors: function(callback)
159     {
160         this._getPropertyDescriptors(false, false, callback);
161     },
162
163     _getPropertyDescriptors: function(ownProperties, ownAndGetterProperties, callback)
164     {
165         if (!this._objectId || this._isSymbol()) {
166             callback([]);
167             return;
168         }
169
170         function remoteObjectBinder(error, properties, internalProperties)
171         {
172             if (error) {
173                 callback(null);
174                 return;
175             }
176
177             if (internalProperties)
178                 properties = properties.concat(internalProperties);
179
180             var descriptors = properties.map(function(payload) {
181                 return WebInspector.PropertyDescriptor.fromPayload(payload);
182             });
183
184             callback(descriptors);
185         }
186
187         // COMPATIBILITY (iOS 8): RuntimeAgent.getProperties did not support ownerAndGetterProperties.
188         // Here we do our best to reimplement it by getting all properties and reducing them down.
189         if (ownAndGetterProperties && !RuntimeAgent.getProperties.supports("ownAndGetterProperties")) {
190             RuntimeAgent.getProperties(this._objectId, function(error, allProperties) {
191                 var ownOrGetterPropertiesList = [];
192                 if (allProperties) {
193                     for (var property of allProperties) {
194                         if (property.isOwn || property.get || property.name === "__proto__") {
195                             // Own property or getter property in prototype chain.
196                             ownOrGetterPropertiesList.push(property);
197                         } else if (property.value && property.name !== property.name.toUpperCase()) {
198                             var type = property.value.type;
199                             if (type && type !== "function" && property.name !== "constructor") {
200                                 // Possible native binding getter property converted to a value. Also, no CONSTANT name style and not "constructor".
201                                 ownOrGetterPropertiesList.push(property);
202                             }
203                         }
204                     }
205                 }
206                 remoteObjectBinder(error, ownOrGetterPropertiesList);
207             });
208             return;
209         }
210
211         RuntimeAgent.getProperties(this._objectId, ownProperties, ownAndGetterProperties, remoteObjectBinder);
212     },
213
214     // FIXME: Phase out these functions. They return RemoteObjectProperty instead of PropertyDescriptors.
215
216     getOwnProperties: function(callback)
217     {
218         this._getProperties(true, false, callback);
219     },
220
221     getOwnAndGetterProperties: function(callback)
222     {
223         this._getProperties(false, true, callback);
224     },
225
226     getAllProperties: function(callback)
227     {
228         this._getProperties(false, false, callback);
229     },
230
231     _getProperties: function(ownProperties, ownAndGetterProperties, callback)
232     {
233         if (!this._objectId || this._isSymbol()) {
234             callback([]);
235             return;
236         }
237
238         function remoteObjectBinder(error, properties, internalProperties)
239         {
240             if (error) {
241                 callback(null);
242                 return;
243             }
244
245             // FIXME: WebInspector.PropertyDescriptor instead of RemoteObjectProperty.
246             if (internalProperties) {
247                 properties = properties.concat(internalProperties.map(function(descriptor) {
248                     descriptor.writable = false;
249                     descriptor.configurable = false;
250                     descriptor.enumerable = false;
251                     descriptor.isOwn = true;
252                     return descriptor;
253                 }));
254             }
255
256             var result = [];
257             for (var i = 0; properties && i < properties.length; ++i) {
258                 var property = properties[i];
259                 if (property.get || property.set) {
260                     if (property.get)
261                         result.push(new WebInspector.RemoteObjectProperty("get " + property.name, WebInspector.RemoteObject.fromPayload(property.get), property));
262                     if (property.set)
263                         result.push(new WebInspector.RemoteObjectProperty("set " + property.name, WebInspector.RemoteObject.fromPayload(property.set), property));
264                 } else
265                     result.push(new WebInspector.RemoteObjectProperty(property.name, WebInspector.RemoteObject.fromPayload(property.value), property));
266             }
267             callback(result);
268         }
269
270         // COMPATIBILITY (iOS 8): RuntimeAgent.getProperties did not support ownerAndGetterProperties.
271         // Here we do our best to reimplement it by getting all properties and reducing them down.
272         if (ownAndGetterProperties && !RuntimeAgent.getProperties.supports("ownAndGetterProperties")) {
273             RuntimeAgent.getProperties(this._objectId, function(error, allProperties) {
274                 var ownOrGetterPropertiesList = [];
275                 if (allProperties) {
276                     for (var property of allProperties) {
277                         if (property.isOwn || property.get || property.name === "__proto__") {
278                             // Own property or getter property in prototype chain.
279                             ownOrGetterPropertiesList.push(property);
280                         } else if (property.value && property.name !== property.name.toUpperCase()) {
281                             var type = property.value.type;
282                             if (type && type !== "function" && property.name !== "constructor") {
283                                 // Possible native binding getter property converted to a value. Also, no CONSTANT name style and not "constructor".
284                                 ownOrGetterPropertiesList.push(property);
285                             }
286                         }
287                     }
288                 }
289                 remoteObjectBinder(error, ownOrGetterPropertiesList);
290             });
291             return;
292         }
293
294         RuntimeAgent.getProperties(this._objectId, ownProperties, ownAndGetterProperties, remoteObjectBinder);
295     },
296
297     setPropertyValue: function(name, value, callback)
298     {
299         if (!this._objectId || this._isSymbol()) {
300             callback("Can't set a property of non-object.");
301             return;
302         }
303
304         RuntimeAgent.evaluate.invoke({expression:value, doNotPauseOnExceptionsAndMuteConsole:true}, evaluatedCallback.bind(this));
305
306         function evaluatedCallback(error, result, wasThrown)
307         {
308             if (error || wasThrown) {
309                 callback(error || result.description);
310                 return;
311             }
312
313             function setPropertyValue(propertyName, propertyValue)
314             {
315                 this[propertyName] = propertyValue;
316             }
317
318             delete result.description; // Optimize on traffic.
319             RuntimeAgent.callFunctionOn(this._objectId, setPropertyValue.toString(), [{ value:name }, result], true, undefined, propertySetCallback.bind(this));
320             if (result._objectId)
321                 RuntimeAgent.releaseObject(result._objectId);
322         }
323
324         function propertySetCallback(error, result, wasThrown)
325         {
326             if (error || wasThrown) {
327                 callback(error || result.description);
328                 return;
329             }
330             callback();
331         }
332     },
333
334     _isSymbol: function()
335     {
336         return this._type === "symbol";
337     },
338
339     isCollectionType: function()
340     {
341         return this._subtype === "map" || this._subtype === "set" || this._subtype === "weakmap";
342     },
343
344     isWeakCollection: function()
345     {
346         return this._subtype === "weakmap";
347     },
348
349     getCollectionEntries: function(start, numberToFetch, callback)
350     {
351         start = typeof start === "number" ? start : 0;
352         numberToFetch = typeof numberToFetch === "number" ? numberToFetch : 100;
353
354         console.assert(start >= 0);
355         console.assert(numberToFetch >= 0);
356         console.assert(this.isCollectionType());
357
358         // WeakMaps are not ordered. We should never send a non-zero start.
359         console.assert((this._subtype === "weakmap" && start === 0) || this._subtype !== "weakmap");
360
361         var objectGroup = this.isWeakCollection() ? this._weakCollectionObjectGroup() : "";
362
363         RuntimeAgent.getCollectionEntries(this._objectId, objectGroup, start, numberToFetch, function(error, entries) {
364             entries = entries.map(function(entry) { return WebInspector.CollectionEntry.fromPayload(entry); });
365             callback(entries);
366         });
367     },
368
369     releaseWeakCollectionEntries: function()
370     {
371         console.assert(this.isWeakCollection());
372
373         RuntimeAgent.releaseObjectGroup(this._weakCollectionObjectGroup());
374     },
375
376     pushNodeToFrontend: function(callback)
377     {
378         if (this._objectId)
379             WebInspector.domTreeManager.pushNodeToFrontend(this._objectId, callback);
380         else
381             callback(0);
382     },
383
384     callFunction: function(functionDeclaration, args, callback)
385     {
386         function mycallback(error, result, wasThrown)
387         {
388             callback((error || wasThrown) ? null : WebInspector.RemoteObject.fromPayload(result));
389         }
390
391         RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, undefined, mycallback);
392     },
393
394     callFunctionJSON: function(functionDeclaration, args, callback)
395     {
396         function mycallback(error, result, wasThrown)
397         {
398             callback((error || wasThrown) ? null : result.value);
399         }
400
401         RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, true, mycallback);
402     },
403
404     release: function()
405     {
406         RuntimeAgent.releaseObject(this._objectId);
407     },
408
409     arrayLength: function()
410     {
411         if (this._subtype !== "array")
412             return 0;
413
414         var matches = this._description.match(/\[([0-9]+)\]/);
415         if (!matches)
416             return 0;
417
418         return parseInt(matches[1], 10);
419     },
420
421     // Private
422
423     _weakCollectionObjectGroup: function()
424     {
425         return JSON.stringify(this._objectId) + "-WeakMap";
426     }
427 };
428
429 WebInspector.RemoteObjectProperty = function(name, value, descriptor)
430 {
431     this.name = name;
432     this.value = value;
433     this.enumerable = descriptor ? !!descriptor.enumerable : true;
434     this.writable = descriptor ? !!descriptor.writable : true;
435     if (descriptor && descriptor.wasThrown)
436         this.wasThrown = true;
437 };
438
439 WebInspector.RemoteObjectProperty.fromPrimitiveValue = function(name, value)
440 {
441     return new WebInspector.RemoteObjectProperty(name, WebInspector.RemoteObject.fromPrimitiveValue(value));
442 };