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