3d3f9417d4cc74513738512483fe03fc60d4a1e6
[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 = payload.type;
76             payload.preview.subtype = payload.subtype;
77             payload.preview.description = payload.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.createCallArgument = function(valueOrObject)
86 {
87     if (valueOrObject instanceof WebInspector.RemoteObject) {
88         if (valueOrObject.objectId)
89             return {objectId: valueOrObject.objectId};
90         return {value: valueOrObject.value};
91     }
92
93     return {value: valueOrObject};
94 };
95
96 WebInspector.RemoteObject.resolveNode = function(node, objectGroup, callback)
97 {
98     DOMAgent.resolveNode(node.id, objectGroup, function(error, object) {
99         if (!callback)
100             return;
101
102         if (error || !object)
103             callback(null);
104         else
105             callback(WebInspector.RemoteObject.fromPayload(object));
106     });
107 };
108
109 WebInspector.RemoteObject.type = function(remoteObject)
110 {
111     if (remoteObject === null)
112         return "null";
113
114     var type = typeof remoteObject;
115     if (type !== "object" && type !== "function")
116         return type;
117
118     return remoteObject.type;
119 };
120
121 WebInspector.RemoteObject.prototype = {
122     constructor: WebInspector.RemoteObject,
123
124     get objectId()
125     {
126         return this._objectId;
127     },
128
129     get type()
130     {
131         return this._type;
132     },
133
134     get subtype()
135     {
136         return this._subtype;
137     },
138
139     get description()
140     {
141         return this._description;
142     },
143
144     get hasChildren()
145     {
146         return this._hasChildren;
147     },
148
149     get value()
150     {
151         return this._value;
152     },
153
154     get preview()
155     {
156         return this._preview;
157     },
158
159     hasValue: function()
160     {
161         return "_value" in this;
162     },
163
164     getOwnPropertyDescriptors: function(callback)
165     {
166         this._getPropertyDescriptors(true, callback);
167     },
168
169     getAllPropertyDescriptors: function(callback)
170     {
171         this._getPropertyDescriptors(false, callback);
172     },
173
174     getDisplayablePropertyDescriptors: function(callback)
175     {
176         if (!this._objectId || this._isSymbol()) {
177             callback([]);
178             return;
179         }
180
181         // COMPATIBILITY (iOS 8): RuntimeAgent.getDisplayableProperties did not exist.
182         // Here we do our best to reimplement it by getting all properties and reducing them down.
183         if (!RuntimeAgent.getDisplayableProperties) {
184             RuntimeAgent.getProperties(this._objectId, function(error, allProperties) {
185                 var ownOrGetterPropertiesList = [];
186                 if (allProperties) {
187                     for (var property of allProperties) {
188                         if (property.isOwn || property.name === "__proto__") {
189                             // Own property or getter property in prototype chain.
190                             ownOrGetterPropertiesList.push(property);
191                         } else if (property.value && property.name !== property.name.toUpperCase()) {
192                             var type = property.value.type;
193                             if (type && type !== "function" && property.name !== "constructor") {
194                                 // Possible native binding getter property converted to a value. Also, no CONSTANT name style and not "constructor".
195                                 // There is no way of knowing if this is native or not, so just go with it.
196                                 ownOrGetterPropertiesList.push(property);
197                             }
198                         }
199                     }
200                 }
201                 this._getPropertyDescriptorsResolver(callback, error, ownOrGetterPropertiesList);
202             }.bind(this));
203             return;
204         }
205
206         RuntimeAgent.getDisplayableProperties(this._objectId, true, this._getPropertyDescriptorsResolver.bind(this, callback));
207     },
208
209     _getPropertyDescriptors: function(ownProperties, callback)
210     {
211         if (!this._objectId || this._isSymbol()) {
212             callback([]);
213             return;
214         }
215
216
217         RuntimeAgent.getProperties(this._objectId, ownProperties, true, this._getPropertyDescriptorsResolver.bind(this, callback));
218     },
219
220     _getPropertyDescriptorsResolver: function(callback, error, properties, internalProperties)
221     {
222         if (error) {
223             callback(null);
224             return;
225         }
226
227         var descriptors = properties.map(function(payload) {
228             return WebInspector.PropertyDescriptor.fromPayload(payload);
229         });
230
231         if (internalProperties) {
232             descriptors = descriptors.concat(internalProperties.map(function(payload) {
233                 return WebInspector.PropertyDescriptor.fromPayload(payload, true);
234             }));
235         }
236
237         callback(descriptors);
238     },
239
240     // FIXME: Phase out these functions. They return RemoteObjectProperty instead of PropertyDescriptors.
241
242     deprecatedGetOwnProperties: function(callback)
243     {
244         this._deprecatedGetProperties(true, callback);
245     },
246
247     deprecatedGetAllProperties: function(callback)
248     {
249         this._deprecatedGetProperties(false, callback);
250     },
251
252     _deprecatedGetProperties: function(ownProperties, callback)
253     {
254         if (!this._objectId || this._isSymbol()) {
255             callback([]);
256             return;
257         }
258
259         RuntimeAgent.getProperties(this._objectId, ownProperties, this._deprecatedGetPropertiesResolver.bind(this, callback));
260     },
261
262     deprecatedGetDisplayableProperties: function(callback)
263     {
264         if (!this._objectId || this._isSymbol()) {
265             callback([]);
266             return;
267         }
268
269         // COMPATIBILITY (iOS 8): RuntimeAgent.getProperties did not support ownerAndGetterProperties.
270         // Here we do our best to reimplement it by getting all properties and reducing them down.
271         if (!RuntimeAgent.getDisplayableProperties) {
272             RuntimeAgent.getProperties(this._objectId, function(error, allProperties) {
273                 var ownOrGetterPropertiesList = [];
274                 if (allProperties) {
275                     for (var property of allProperties) {
276                         if (property.isOwn || property.get || property.name === "__proto__") {
277                             // Own property or getter property in prototype chain.
278                             ownOrGetterPropertiesList.push(property);
279                         } else if (property.value && property.name !== property.name.toUpperCase()) {
280                             var type = property.value.type;
281                             if (type && type !== "function" && property.name !== "constructor") {
282                                 // Possible native binding getter property converted to a value. Also, no CONSTANT name style and not "constructor".
283                                 ownOrGetterPropertiesList.push(property);
284                             }
285                         }
286                     }
287                 }
288                 this._deprecatedGetPropertiesResolver(callback, error, ownOrGetterPropertiesList);
289             }.bind(this));
290             return;
291         }
292
293         RuntimeAgent.getDisplayableProperties(this._objectId, this._deprecatedGetPropertiesResolver.bind(this, callback));
294     },
295
296     _deprecatedGetPropertiesResolver: function(callback, error, properties, internalProperties)
297     {
298         if (error) {
299             callback(null);
300             return;
301         }
302
303         if (internalProperties) {
304             properties = properties.concat(internalProperties.map(function(descriptor) {
305                 descriptor.writable = false;
306                 descriptor.configurable = false;
307                 descriptor.enumerable = false;
308                 descriptor.isOwn = true;
309                 return descriptor;
310             }));
311         }
312
313         var result = [];
314         for (var i = 0; properties && i < properties.length; ++i) {
315             var property = properties[i];
316             if (property.get || property.set) {
317                 if (property.get)
318                     result.push(new WebInspector.RemoteObjectProperty("get " + property.name, WebInspector.RemoteObject.fromPayload(property.get), property));
319                 if (property.set)
320                     result.push(new WebInspector.RemoteObjectProperty("set " + property.name, WebInspector.RemoteObject.fromPayload(property.set), property));
321             } else
322                 result.push(new WebInspector.RemoteObjectProperty(property.name, WebInspector.RemoteObject.fromPayload(property.value), property));
323         }
324         callback(result);
325     },
326
327     setPropertyValue: function(name, value, callback)
328     {
329         if (!this._objectId || this._isSymbol()) {
330             callback("Can't set a property of non-object.");
331             return;
332         }
333
334         RuntimeAgent.evaluate.invoke({expression:value, doNotPauseOnExceptionsAndMuteConsole:true}, evaluatedCallback.bind(this));
335
336         function evaluatedCallback(error, result, wasThrown)
337         {
338             if (error || wasThrown) {
339                 callback(error || result.description);
340                 return;
341             }
342
343             function setPropertyValue(propertyName, propertyValue)
344             {
345                 this[propertyName] = propertyValue;
346             }
347
348             delete result.description; // Optimize on traffic.
349             RuntimeAgent.callFunctionOn(this._objectId, setPropertyValue.toString(), [{ value:name }, result], true, undefined, propertySetCallback.bind(this));
350             if (result._objectId)
351                 RuntimeAgent.releaseObject(result._objectId);
352         }
353
354         function propertySetCallback(error, result, wasThrown)
355         {
356             if (error || wasThrown) {
357                 callback(error || result.description);
358                 return;
359             }
360             callback();
361         }
362     },
363
364     _isSymbol: function()
365     {
366         return this._type === "symbol";
367     },
368
369     isArray: function()
370     {
371         return this._subtype === "array";
372     },
373
374     isCollectionType: function()
375     {
376         return this._subtype === "map" || this._subtype === "set" || this._subtype === "weakmap";
377     },
378
379     isWeakCollection: function()
380     {
381         return this._subtype === "weakmap";
382     },
383
384     getCollectionEntries: function(start, numberToFetch, callback)
385     {
386         start = typeof start === "number" ? start : 0;
387         numberToFetch = typeof numberToFetch === "number" ? numberToFetch : 100;
388
389         console.assert(start >= 0);
390         console.assert(numberToFetch >= 0);
391         console.assert(this.isCollectionType());
392
393         // WeakMaps are not ordered. We should never send a non-zero start.
394         console.assert((this._subtype === "weakmap" && start === 0) || this._subtype !== "weakmap");
395
396         var objectGroup = this.isWeakCollection() ? this._weakCollectionObjectGroup() : "";
397
398         RuntimeAgent.getCollectionEntries(this._objectId, objectGroup, start, numberToFetch, function(error, entries) {
399             entries = entries.map(function(entry) { return WebInspector.CollectionEntry.fromPayload(entry); });
400             callback(entries);
401         });
402     },
403
404     releaseWeakCollectionEntries: function()
405     {
406         console.assert(this.isWeakCollection());
407
408         RuntimeAgent.releaseObjectGroup(this._weakCollectionObjectGroup());
409     },
410
411     pushNodeToFrontend: function(callback)
412     {
413         if (this._objectId)
414             WebInspector.domTreeManager.pushNodeToFrontend(this._objectId, callback);
415         else
416             callback(0);
417     },
418
419     callFunction: function(functionDeclaration, args, generatePreview, callback)
420     {
421         function mycallback(error, result, wasThrown)
422         {
423             result = result ? WebInspector.RemoteObject.fromPayload(result) : null;
424             callback(error, result, wasThrown);
425         }
426
427         if (args)
428             args = args.map(WebInspector.RemoteObject.createCallArgument);
429
430         RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, undefined, generatePreview, mycallback);
431     },
432
433     callFunctionJSON: function(functionDeclaration, args, callback)
434     {
435         function mycallback(error, result, wasThrown)
436         {
437             callback((error || wasThrown) ? null : result.value);
438         }
439
440         RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, true, mycallback);
441     },
442     
443     invokeGetter: function(getterRemoteObject, callback)
444     {
445         console.assert(getterRemoteObject instanceof WebInspector.RemoteObject);
446
447         function backendInvokeGetter(getter)
448         {
449             return getter ? getter.call(this) : undefined;
450         }
451
452         this.callFunction(backendInvokeGetter, [getterRemoteObject], true, callback);
453     },
454
455     getOwnPropertyDescriptor: function(propertyName, callback)
456     {
457         if (!RuntimeAgent.getOwnPropertyDescriptor) {
458             function backendGetOwnPropertyDescriptor(propertyName)
459             {
460                 return this[propertyName];
461             }
462
463             function wrappedCallback(error, result, wasThrown)
464             {
465                 if (error || wasThrown || !(result instanceof WebInspector.RemoteObject)) {
466                     callback(null);
467                     return;
468                 }
469
470                 var fakeDescriptor = {name: propertyName, value: result, writable: true, configurable: true, enumerable: false};
471                 var fakePropertyDescriptor = new WebInspector.PropertyDescriptor(fakeDescriptor, true, false, false, false);
472                 callback(fakePropertyDescriptor);
473             }
474
475             this.callFunction(backendGetOwnPropertyDescriptor, [propertyName], false, wrappedCallback);
476         }
477
478         // FIXME: Implement a real getOwnPropertyDescriptor?
479     },
480
481     release: function()
482     {
483         RuntimeAgent.releaseObject(this._objectId);
484     },
485
486     arrayLength: function()
487     {
488         if (this._subtype !== "array")
489             return 0;
490
491         var matches = this._description.match(/\[([0-9]+)\]/);
492         if (!matches)
493             return 0;
494
495         return parseInt(matches[1], 10);
496     },
497
498     asCallArgument: function()
499     {
500         return WebInspector.RemoteObject.createCallArgument(this);
501     },
502
503     // Private
504
505     _weakCollectionObjectGroup: function()
506     {
507         return JSON.stringify(this._objectId) + "-WeakMap";
508     }
509 };
510
511 WebInspector.RemoteObjectProperty = function(name, value, descriptor)
512 {
513     this.name = name;
514     this.value = value;
515     this.enumerable = descriptor ? !!descriptor.enumerable : true;
516     this.writable = descriptor ? !!descriptor.writable : true;
517     if (descriptor && descriptor.wasThrown)
518         this.wasThrown = true;
519 };
520
521 WebInspector.RemoteObjectProperty.fromPrimitiveValue = function(name, value)
522 {
523     return new WebInspector.RemoteObjectProperty(name, WebInspector.RemoteObject.fromPrimitiveValue(value));
524 };