7e4637375e8f6cdba55d11e0122d3807c60ca249
[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     this._type = type;
34     this._subtype = subtype;
35     if (objectId) {
36         // handle
37         this._objectId = objectId;
38         this._description = description;
39         this._hasChildren = type !== "symbol";
40         this._preview = preview;
41     } else {
42         // Primitive or null object.
43         console.assert(type !== "object" || value === null);
44         this._description = description || (value + "");
45         this._hasChildren = false;
46         this.value = value;
47     }
48 };
49
50 WebInspector.RemoteObject.fromPrimitiveValue = function(value)
51 {
52     return new WebInspector.RemoteObject(undefined, typeof value, undefined, value);
53 };
54
55 WebInspector.RemoteObject.resolveNode = function(node, objectGroup, callback)
56 {
57     function mycallback(error, object)
58     {
59         if (!callback)
60             return;
61
62         if (error || !object)
63             callback(null);
64         else
65             callback(WebInspector.RemoteObject.fromPayload(object));
66     }
67     DOMAgent.resolveNode(node.id, objectGroup, mycallback);
68 };
69
70 WebInspector.RemoteObject.fromPayload = function(payload)
71 {
72     console.assert(typeof payload === "object", "Remote object payload should only be an object");
73
74     return new WebInspector.RemoteObject(payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
75 };
76
77 WebInspector.RemoteObject.type = function(remoteObject)
78 {
79     if (remoteObject === null)
80         return "null";
81
82     var type = typeof remoteObject;
83     if (type !== "object" && type !== "function")
84         return type;
85
86     return remoteObject.type;
87 };
88
89 WebInspector.RemoteObject.prototype = {
90     get objectId()
91     {
92         return this._objectId;
93     },
94
95     get type()
96     {
97         return this._type;
98     },
99
100     get subtype()
101     {
102         return this._subtype;
103     },
104
105     get description()
106     {
107         return this._description;
108     },
109
110     get hasChildren()
111     {
112         return this._hasChildren;
113     },
114
115     get preview()
116     {
117         return this._preview;
118     },
119
120     getOwnProperties: function(callback)
121     {
122         this._getProperties(true, false, callback);
123     },
124
125     getOwnAndGetterProperties: function(callback)
126     {
127         this._getProperties(false, true, callback);
128     },
129
130     getAllProperties: function(callback)
131     {
132         this._getProperties(false, false, callback);
133     },
134
135     _getProperties: function(ownProperties, ownAndGetterProperties, callback)
136     {
137         if (!this._objectId || this._isSymbol()) {
138             callback([]);
139             return;
140         }
141
142         function remoteObjectBinder(error, properties, internalProperties)
143         {
144             if (error) {
145                 callback(null);
146                 return;
147             }
148
149             // FIXME: We should display Internal Properties visually distinct. For now treat as non-enumerable own properties.
150             if (internalProperties) {
151                 properties = properties.concat(internalProperties.map(function(descriptor) {
152                     descriptor.writable = false;
153                     descriptor.configurable = false;
154                     descriptor.enumerable = false;
155                     descriptor.isOwn = true;
156                     return descriptor;
157                 }));
158             }
159
160             var result = [];
161             for (var i = 0; properties && i < properties.length; ++i) {
162                 var property = properties[i];
163                 if (property.get || property.set) {
164                     if (property.get)
165                         result.push(new WebInspector.RemoteObjectProperty("get " + property.name, WebInspector.RemoteObject.fromPayload(property.get), property));
166                     if (property.set)
167                         result.push(new WebInspector.RemoteObjectProperty("set " + property.name, WebInspector.RemoteObject.fromPayload(property.set), property));
168                 } else
169                     result.push(new WebInspector.RemoteObjectProperty(property.name, WebInspector.RemoteObject.fromPayload(property.value), property));
170             }
171             callback(result);
172         }
173
174         // COMPATIBILITY (iOS 8): RuntimeAgent.getProperties did not support ownerAndGetterProperties.
175         // Here we do our best to reimplement it by getting all properties and reducing them down.
176         if (ownAndGetterProperties && !RuntimeAgent.getProperties.supports("ownAndGetterProperties")) {
177             RuntimeAgent.getProperties(this._objectId, function(error, allProperties) {
178                 var ownOrGetterPropertiesList = [];
179                 if (allProperties) {
180                     for (var property of allProperties) {
181                         if (property.isOwn || property.get || property.name === "__proto__") {
182                             // Own property or getter property in prototype chain.
183                             ownOrGetterPropertiesList.push(property);
184                         } else if (property.value && property.name !== property.name.toUpperCase()) {
185                             var type = property.value.type;
186                             if (type && type !== "function" && property.name !== "constructor") {
187                                 // Possible native binding getter property converted to a value. Also, no CONSTANT name style and not "constructor".
188                                 ownOrGetterPropertiesList.push(property);
189                             }
190                         }
191                     }
192                 }
193                 remoteObjectBinder(error, ownOrGetterPropertiesList);
194             }); 
195             return;
196         }
197
198         RuntimeAgent.getProperties(this._objectId, ownProperties, ownAndGetterProperties, remoteObjectBinder);
199     },
200
201     setPropertyValue: function(name, value, callback)
202     {
203         if (!this._objectId || this._isSymbol()) {
204             callback("Can't set a property of non-object.");
205             return;
206         }
207
208         RuntimeAgent.evaluate.invoke({expression:value, doNotPauseOnExceptionsAndMuteConsole:true}, evaluatedCallback.bind(this));
209
210         function evaluatedCallback(error, result, wasThrown)
211         {
212             if (error || wasThrown) {
213                 callback(error || result.description);
214                 return;
215             }
216
217             function setPropertyValue(propertyName, propertyValue)
218             {
219                 this[propertyName] = propertyValue;
220             }
221
222             delete result.description; // Optimize on traffic.
223             RuntimeAgent.callFunctionOn(this._objectId, setPropertyValue.toString(), [{ value:name }, result], true, undefined, propertySetCallback.bind(this));
224             if (result._objectId)
225                 RuntimeAgent.releaseObject(result._objectId);
226         }
227
228         function propertySetCallback(error, result, wasThrown)
229         {
230             if (error || wasThrown) {
231                 callback(error || result.description);
232                 return;
233             }
234             callback();
235         }
236     },
237
238     _isSymbol: function()
239     {
240         return this.type === "symbol";
241     },
242
243     isCollectionType: function()
244     {
245         return this.subtype === "map" || this.subtype === "set" || this.subtype === "weakmap";
246     },
247
248     isWeakCollection: function()
249     {
250         return this.subtype === "weakmap";
251     },
252
253     getCollectionEntries: function(start, numberToFetch, callback)
254     {
255         start = typeof start === "number" ? start : 0;
256         numberToFetch = typeof numberToFetch === "number" ? numberToFetch : 100;
257
258         console.assert(start >= 0);
259         console.assert(numberToFetch >= 0);
260         console.assert(this.isCollectionType());
261
262         // WeakMaps are not ordered. We should never send a non-zero start.
263         console.assert((this.subtype === "weakmap" && start === 0) || this.subtype !== "weakmap");
264
265         var objectGroup = this.isWeakCollection() ? this._weakCollectionObjectGroup() : "";
266
267         RuntimeAgent.getCollectionEntries(this._objectId, objectGroup, start, numberToFetch, function(error, entries) {
268             callback(entries);
269         });
270     },
271
272     releaseWeakCollectionEntries: function()
273     {
274         console.assert(this.isWeakCollection());
275
276         RuntimeAgent.releaseObjectGroup(this._weakCollectionObjectGroup());
277     },
278
279     pushNodeToFrontend: function(callback)
280     {
281         if (this._objectId)
282             WebInspector.domTreeManager.pushNodeToFrontend(this._objectId, callback);
283         else
284             callback(0);
285     },
286
287     callFunction: function(functionDeclaration, args, callback)
288     {
289         function mycallback(error, result, wasThrown)
290         {
291             callback((error || wasThrown) ? null : WebInspector.RemoteObject.fromPayload(result));
292         }
293
294         RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, undefined, mycallback);
295     },
296
297     callFunctionJSON: function(functionDeclaration, args, callback)
298     {
299         function mycallback(error, result, wasThrown)
300         {
301             callback((error || wasThrown) ? null : result.value);
302         }
303
304         RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, true, mycallback);
305     },
306
307     release: function()
308     {
309         RuntimeAgent.releaseObject(this._objectId);
310     },
311
312     arrayLength: function()
313     {
314         if (this.subtype !== "array")
315             return 0;
316
317         var matches = this._description.match(/\[([0-9]+)\]/);
318         if (!matches)
319             return 0;
320         return parseInt(matches[1], 10);
321     },
322
323     // Private
324
325     _weakCollectionObjectGroup: function()
326     {
327         return JSON.stringify(this._objectId) + "-WeakMap";
328     }
329 };
330
331 WebInspector.RemoteObjectProperty = function(name, value, descriptor)
332 {
333     this.name = name;
334     this.value = value;
335     this.enumerable = descriptor ? !!descriptor.enumerable : true;
336     this.writable = descriptor ? !!descriptor.writable : true;
337     if (descriptor && descriptor.wasThrown)
338         this.wasThrown = true;
339 };
340
341 WebInspector.RemoteObjectProperty.fromPrimitiveValue = function(name, value)
342 {
343     return new WebInspector.RemoteObjectProperty(name, WebInspector.RemoteObject.fromPrimitiveValue(value));
344 };