c5abd0551b2411780468a23033d20c753b4c4f0f
[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 WI.RemoteObject = class RemoteObject
33 {
34     constructor(target, objectId, type, subtype, value, description, size, classPrototype, className, preview)
35     {
36         console.assert(type);
37         console.assert(!preview || preview instanceof WI.ObjectPreview);
38         console.assert(!target || target instanceof WI.Target);
39
40         this._target = target || WI.mainTarget;
41         this._type = type;
42         this._subtype = subtype;
43
44         if (objectId) {
45             // Object, Function, or Symbol.
46             console.assert(!subtype || typeof subtype === "string");
47             console.assert(!description || typeof description === "string");
48             console.assert(!value);
49
50             this._objectId = objectId;
51             this._description = description || "";
52             this._hasChildren = type !== "symbol";
53             this._size = size;
54             this._classPrototype = classPrototype;
55             this._preview = preview;
56
57             if (subtype === "class") {
58                 this._functionDescription = this._description;
59                 this._description = "class " + className;
60             }
61         } else {
62             // Primitive or null.
63             console.assert(type !== "object" || value === null);
64             console.assert(!preview);
65
66             this._description = description || (value + "");
67             this._hasChildren = false;
68             this._value = value;
69         }
70     }
71
72     // Static
73
74     static createFakeRemoteObject()
75     {
76         return new WI.RemoteObject(undefined, WI.RemoteObject.FakeRemoteObjectId, "object");
77     }
78
79     static fromPrimitiveValue(value)
80     {
81         return new WI.RemoteObject(undefined, undefined, typeof value, undefined, value, undefined, undefined, undefined, undefined);
82     }
83
84     static fromPayload(payload, target)
85     {
86         console.assert(typeof payload === "object", "Remote object payload should only be an object");
87
88         if (payload.subtype === "array") {
89             // COMPATIBILITY (iOS 8): Runtime.RemoteObject did not have size property,
90             // instead it was tacked onto the end of the description, like "Array[#]".
91             var match = payload.description.match(/\[(\d+)\]$/);
92             if (match) {
93                 payload.size = parseInt(match[1]);
94                 payload.description = payload.description.replace(/\[\d+\]$/, "");
95             }
96         }
97
98         if (payload.classPrototype)
99             payload.classPrototype = WI.RemoteObject.fromPayload(payload.classPrototype, target);
100
101         if (payload.preview) {
102             // COMPATIBILITY (iOS 8): iOS 7 and 8 did not have type/subtype/description on
103             // Runtime.ObjectPreview. Copy them over from the RemoteObject.
104             if (!payload.preview.type) {
105                 payload.preview.type = payload.type;
106                 payload.preview.subtype = payload.subtype;
107                 payload.preview.description = payload.description;
108                 payload.preview.size = payload.size;
109             }
110
111             payload.preview = WI.ObjectPreview.fromPayload(payload.preview);
112         }
113
114         return new WI.RemoteObject(target, payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.size, payload.classPrototype, payload.className, payload.preview);
115     }
116
117     static createCallArgument(valueOrObject)
118     {
119         if (valueOrObject instanceof WI.RemoteObject) {
120             if (valueOrObject.objectId)
121                 return {objectId: valueOrObject.objectId};
122             return {value: valueOrObject.value};
123         }
124
125         return {value: valueOrObject};
126     }
127
128     static resolveNode(node, objectGroup)
129     {
130         return DOMAgent.resolveNode(node.id, objectGroup)
131             .then(({object}) => WI.RemoteObject.fromPayload(object, WI.mainTarget));
132     }
133
134     static resolveWebSocket(webSocketResource, objectGroup, callback)
135     {
136         console.assert(typeof callback === "function");
137
138         NetworkAgent.resolveWebSocket(webSocketResource.requestIdentifier, objectGroup, (error, object) => {
139             if (error || !object)
140                 callback(null);
141             else
142                 callback(WI.RemoteObject.fromPayload(object, webSocketResource.target));
143         });
144     }
145
146     static resolveCanvasContext(canvas, objectGroup, callback)
147     {
148         console.assert(typeof callback === "function");
149
150         CanvasAgent.resolveCanvasContext(canvas.identifier, objectGroup, (error, object) => {
151             if (error || !object)
152                 callback(null);
153             else
154                 callback(WI.RemoteObject.fromPayload(object, WI.mainTarget));
155         });
156     }
157
158     // Public
159
160     get target()
161     {
162         return this._target;
163     }
164
165     get objectId()
166     {
167         return this._objectId;
168     }
169
170     get type()
171     {
172         return this._type;
173     }
174
175     get subtype()
176     {
177         return this._subtype;
178     }
179
180     get description()
181     {
182         return this._description;
183     }
184
185     get functionDescription()
186     {
187         console.assert(this.type === "function");
188
189         return this._functionDescription || this._description;
190     }
191
192     get hasChildren()
193     {
194         return this._hasChildren;
195     }
196
197     get value()
198     {
199         return this._value;
200     }
201
202     get size()
203     {
204         return this._size || 0;
205     }
206
207     get classPrototype()
208     {
209         return this._classPrototype;
210     }
211
212     get preview()
213     {
214         return this._preview;
215     }
216
217     hasSize()
218     {
219         return this.isArray() || this.isCollectionType();
220     }
221
222     hasValue()
223     {
224         return "_value" in this;
225     }
226
227     canLoadPreview()
228     {
229         if (this._failedToLoadPreview)
230             return false;
231
232         if (this._type !== "object")
233             return false;
234
235         if (!this._objectId || this._isSymbol() || this._isFakeObject())
236             return false;
237
238         return true;
239     }
240
241     updatePreview(callback)
242     {
243         if (!this.canLoadPreview()) {
244             callback(null);
245             return;
246         }
247
248         if (!RuntimeAgent.getPreview) {
249             this._failedToLoadPreview = true;
250             callback(null);
251             return;
252         }
253
254         this._target.RuntimeAgent.getPreview(this._objectId, (error, payload) => {
255             if (error) {
256                 this._failedToLoadPreview = true;
257                 callback(null);
258                 return;
259             }
260
261             this._preview = WI.ObjectPreview.fromPayload(payload);
262             callback(this._preview);
263         });
264     }
265
266     getOwnPropertyDescriptors(callback)
267     {
268         this._getPropertyDescriptors(true, callback);
269     }
270
271     getAllPropertyDescriptors(callback)
272     {
273         this._getPropertyDescriptors(false, callback);
274     }
275
276     getDisplayablePropertyDescriptors(callback)
277     {
278         if (!this._objectId || this._isSymbol() || this._isFakeObject()) {
279             callback([]);
280             return;
281         }
282
283         // COMPATIBILITY (iOS 8): RuntimeAgent.getDisplayableProperties did not exist.
284         // Here we do our best to reimplement it by getting all properties and reducing them down.
285         if (!RuntimeAgent.getDisplayableProperties) {
286             this._target.RuntimeAgent.getProperties(this._objectId, function(error, allProperties) {
287                 var ownOrGetterPropertiesList = [];
288                 if (allProperties) {
289                     for (var property of allProperties) {
290                         if (property.isOwn || property.name === "__proto__") {
291                             // Own property or getter property in prototype chain.
292                             ownOrGetterPropertiesList.push(property);
293                         } else if (property.value && property.name !== property.name.toUpperCase()) {
294                             var type = property.value.type;
295                             if (type && type !== "function" && property.name !== "constructor") {
296                                 // Possible native binding getter property converted to a value. Also, no CONSTANT name style and not "constructor".
297                                 // There is no way of knowing if this is native or not, so just go with it.
298                                 ownOrGetterPropertiesList.push(property);
299                             }
300                         }
301                     }
302                 }
303
304                 this._getPropertyDescriptorsResolver(callback, error, ownOrGetterPropertiesList);
305             }.bind(this));
306             return;
307         }
308
309         this._target.RuntimeAgent.getDisplayableProperties(this._objectId, true, this._getPropertyDescriptorsResolver.bind(this, callback));
310     }
311
312     // FIXME: Phase out these deprecated functions. They return DeprecatedRemoteObjectProperty instead of PropertyDescriptors.
313     deprecatedGetOwnProperties(callback)
314     {
315         this._deprecatedGetProperties(true, callback);
316     }
317
318     deprecatedGetAllProperties(callback)
319     {
320         this._deprecatedGetProperties(false, callback);
321     }
322
323     deprecatedGetDisplayableProperties(callback)
324     {
325         if (!this._objectId || this._isSymbol() || this._isFakeObject()) {
326             callback([]);
327             return;
328         }
329
330         // COMPATIBILITY (iOS 8): RuntimeAgent.getProperties did not support ownerAndGetterProperties.
331         // Here we do our best to reimplement it by getting all properties and reducing them down.
332         if (!RuntimeAgent.getDisplayableProperties) {
333             this._target.RuntimeAgent.getProperties(this._objectId, function(error, allProperties) {
334                 var ownOrGetterPropertiesList = [];
335                 if (allProperties) {
336                     for (var property of allProperties) {
337                         if (property.isOwn || property.get || property.name === "__proto__") {
338                             // Own property or getter property in prototype chain.
339                             ownOrGetterPropertiesList.push(property);
340                         } else if (property.value && property.name !== property.name.toUpperCase()) {
341                             var type = property.value.type;
342                             if (type && type !== "function" && property.name !== "constructor") {
343                                 // Possible native binding getter property converted to a value. Also, no CONSTANT name style and not "constructor".
344                                 ownOrGetterPropertiesList.push(property);
345                             }
346                         }
347                     }
348                 }
349
350                 this._deprecatedGetPropertiesResolver(callback, error, ownOrGetterPropertiesList);
351             }.bind(this));
352             return;
353         }
354
355         this._target.RuntimeAgent.getDisplayableProperties(this._objectId, this._deprecatedGetPropertiesResolver.bind(this, callback));
356     }
357
358     setPropertyValue(name, value, callback)
359     {
360         if (!this._objectId || this._isSymbol() || this._isFakeObject()) {
361             callback("Can't set a property of non-object.");
362             return;
363         }
364
365         // FIXME: It doesn't look like setPropertyValue is used yet. This will need to be tested when it is again (editable ObjectTrees).
366         this._target.RuntimeAgent.evaluate.invoke({expression: appendWebInspectorSourceURL(value), doNotPauseOnExceptionsAndMuteConsole: true}, evaluatedCallback.bind(this), this._target.RuntimeAgent);
367
368         function evaluatedCallback(error, result, wasThrown)
369         {
370             if (error || wasThrown) {
371                 callback(error || result.description);
372                 return;
373             }
374
375             function setPropertyValue(propertyName, propertyValue)
376             {
377                 this[propertyName] = propertyValue;
378             }
379
380             delete result.description; // Optimize on traffic.
381
382             this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(setPropertyValue.toString()), [{value: name}, result], true, undefined, propertySetCallback.bind(this));
383
384             if (result._objectId)
385                 this._target.RuntimeAgent.releaseObject(result._objectId);
386         }
387
388         function propertySetCallback(error, result, wasThrown)
389         {
390             if (error || wasThrown) {
391                 callback(error || result.description);
392                 return;
393             }
394
395             callback();
396         }
397     }
398
399     isUndefined()
400     {
401         return this._type === "undefined";
402     }
403
404     isNode()
405     {
406         return this._subtype === "node";
407     }
408
409     isArray()
410     {
411         return this._subtype === "array";
412     }
413
414     isClass()
415     {
416         return this._subtype === "class";
417     }
418
419     isCollectionType()
420     {
421         return this._subtype === "map" || this._subtype === "set" || this._subtype === "weakmap" || this._subtype === "weakset";
422     }
423
424     isWeakCollection()
425     {
426         return this._subtype === "weakmap" || this._subtype === "weakset";
427     }
428
429     getCollectionEntries(start, numberToFetch, callback)
430     {
431         start = typeof start === "number" ? start : 0;
432         numberToFetch = typeof numberToFetch === "number" ? numberToFetch : 100;
433
434         console.assert(start >= 0);
435         console.assert(numberToFetch >= 0);
436         console.assert(this.isCollectionType());
437
438         // WeakMaps and WeakSets are not ordered. We should never send a non-zero start.
439         console.assert((this._subtype === "weakmap" && start === 0) || this._subtype !== "weakmap");
440         console.assert((this._subtype === "weakset" && start === 0) || this._subtype !== "weakset");
441
442         let objectGroup = this.isWeakCollection() ? this._weakCollectionObjectGroup() : "";
443
444         this._target.RuntimeAgent.getCollectionEntries(this._objectId, objectGroup, start, numberToFetch, (error, entries) => {
445             entries = entries.map((x) => WI.CollectionEntry.fromPayload(x, this._target));
446             callback(entries);
447         });
448     }
449
450     releaseWeakCollectionEntries()
451     {
452         console.assert(this.isWeakCollection());
453
454         this._target.RuntimeAgent.releaseObjectGroup(this._weakCollectionObjectGroup());
455     }
456
457     pushNodeToFrontend(callback)
458     {
459         if (this._objectId)
460             WI.domTreeManager.pushNodeToFrontend(this._objectId, callback);
461         else
462             callback(0);
463     }
464
465     getProperty(propertyName, callback)
466     {
467         function inspectedPage_object_getProperty(property) {
468             return this[property];
469         }
470
471         this.callFunction(inspectedPage_object_getProperty, [propertyName], true, callback);
472     }
473
474     callFunction(functionDeclaration, args, generatePreview, callback)
475     {
476         function mycallback(error, result, wasThrown)
477         {
478             result = result ? WI.RemoteObject.fromPayload(result, this._target) : null;
479
480             if (callback && typeof callback === "function")
481                 callback(error, result, wasThrown);
482         }
483
484         if (args)
485             args = args.map(WI.RemoteObject.createCallArgument);
486
487         this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(functionDeclaration.toString()), args, true, undefined, !!generatePreview, mycallback.bind(this));
488     }
489
490     callFunctionJSON(functionDeclaration, args, callback)
491     {
492         function mycallback(error, result, wasThrown)
493         {
494             callback((error || wasThrown) ? null : result.value);
495         }
496
497         this._target.RuntimeAgent.callFunctionOn(this._objectId, appendWebInspectorSourceURL(functionDeclaration.toString()), args, true, true, mycallback);
498     }
499
500     invokeGetter(getterRemoteObject, callback)
501     {
502         console.assert(getterRemoteObject instanceof WI.RemoteObject);
503
504         function backendInvokeGetter(getter)
505         {
506             return getter ? getter.call(this) : undefined;
507         }
508
509         this.callFunction(backendInvokeGetter, [getterRemoteObject], true, callback);
510     }
511
512     getOwnPropertyDescriptor(propertyName, callback)
513     {
514         function backendGetOwnPropertyDescriptor(propertyName)
515         {
516             return this[propertyName];
517         }
518
519         function wrappedCallback(error, result, wasThrown)
520         {
521             if (error || wasThrown || !(result instanceof WI.RemoteObject)) {
522                 callback(null);
523                 return;
524             }
525
526             var fakeDescriptor = {name: propertyName, value: result, writable: true, configurable: true, enumerable: false};
527             var fakePropertyDescriptor = new WI.PropertyDescriptor(fakeDescriptor, null, true, false, false, false);
528             callback(fakePropertyDescriptor);
529         }
530
531         // FIXME: Implement a real RuntimeAgent.getOwnPropertyDescriptor?
532         this.callFunction(backendGetOwnPropertyDescriptor, [propertyName], false, wrappedCallback.bind(this));
533     }
534
535     release()
536     {
537         if (this._objectId && !this._isFakeObject())
538             this._target.RuntimeAgent.releaseObject(this._objectId);
539     }
540
541     arrayLength()
542     {
543         if (this._subtype !== "array")
544             return 0;
545
546         var matches = this._description.match(/\[([0-9]+)\]/);
547         if (!matches)
548             return 0;
549
550         return parseInt(matches[1], 10);
551     }
552
553     asCallArgument()
554     {
555         return WI.RemoteObject.createCallArgument(this);
556     }
557
558     findFunctionSourceCodeLocation()
559     {
560         var result = new WI.WrappedPromise;
561
562         if (!this._isFunction() || !this._objectId) {
563             result.resolve(WI.RemoteObject.SourceCodeLocationPromise.MissingObjectId);
564             return result.promise;
565         }
566
567         this._target.DebuggerAgent.getFunctionDetails(this._objectId, (error, response) => {
568             if (error) {
569                 result.reject(error);
570                 return;
571             }
572
573             var location = response.location;
574             var sourceCode = WI.debuggerManager.scriptForIdentifier(location.scriptId, this._target);
575
576             if (!sourceCode || (!WI.isDebugUIEnabled() && isWebKitInternalScript(sourceCode.sourceURL))) {
577                 result.resolve(WI.RemoteObject.SourceCodeLocationPromise.NoSourceFound);
578                 return;
579             }
580
581             var sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber || 0);
582             result.resolve(sourceCodeLocation);
583         });
584
585         return result.promise;
586     }
587
588     // Private
589
590     _isFakeObject()
591     {
592         return this._objectId === WI.RemoteObject.FakeRemoteObjectId;
593     }
594
595     _isSymbol()
596     {
597         return this._type === "symbol";
598     }
599
600     _isFunction()
601     {
602         return this._type === "function";
603     }
604
605     _weakCollectionObjectGroup()
606     {
607         return JSON.stringify(this._objectId) + "-" + this._subtype;
608     }
609
610     _getPropertyDescriptors(ownProperties, callback)
611     {
612         if (!this._objectId || this._isSymbol() || this._isFakeObject()) {
613             callback([]);
614             return;
615         }
616
617         this._target.RuntimeAgent.getProperties(this._objectId, ownProperties, true, this._getPropertyDescriptorsResolver.bind(this, callback));
618     }
619
620     getOwnPropertyDescriptorsAsObject(callback)
621     {
622         this.getOwnPropertyDescriptors(function(properties) {
623             var propertiesResult = {};
624             var internalPropertiesResult = {};
625             for (var propertyDescriptor of properties) {
626                 var object = propertyDescriptor.isInternalProperty ? internalPropertiesResult : propertiesResult;
627                 object[propertyDescriptor.name] = propertyDescriptor;
628             }
629             callback(propertiesResult, internalPropertiesResult);
630         });
631     }
632
633     _getPropertyDescriptorsResolver(callback, error, properties, internalProperties)
634     {
635         if (error) {
636             callback(null);
637             return;
638         }
639
640         let descriptors = properties.map((payload) => {
641             return WI.PropertyDescriptor.fromPayload(payload, false, this._target);
642         });
643
644         if (internalProperties) {
645             descriptors = descriptors.concat(internalProperties.map((payload) => {
646                 return WI.PropertyDescriptor.fromPayload(payload, true, this._target);
647             }));
648         }
649
650         callback(descriptors);
651     }
652
653     // FIXME: Phase out these deprecated functions. They return DeprecatedRemoteObjectProperty instead of PropertyDescriptors.
654     _deprecatedGetProperties(ownProperties, callback)
655     {
656         if (!this._objectId || this._isSymbol() || this._isFakeObject()) {
657             callback([]);
658             return;
659         }
660
661         this._target.RuntimeAgent.getProperties(this._objectId, ownProperties, this._deprecatedGetPropertiesResolver.bind(this, callback));
662     }
663
664     _deprecatedGetPropertiesResolver(callback, error, properties, internalProperties)
665     {
666         if (error) {
667             callback(null);
668             return;
669         }
670
671         if (internalProperties) {
672             properties = properties.concat(internalProperties.map(function(descriptor) {
673                 descriptor.writable = false;
674                 descriptor.configurable = false;
675                 descriptor.enumerable = false;
676                 descriptor.isOwn = true;
677                 return descriptor;
678             }));
679         }
680
681         var result = [];
682         for (var i = 0; properties && i < properties.length; ++i) {
683             var property = properties[i];
684             if (property.get || property.set) {
685                 if (property.get)
686                     result.push(new WI.DeprecatedRemoteObjectProperty("get " + property.name, WI.RemoteObject.fromPayload(property.get, this._target), property));
687                 if (property.set)
688                     result.push(new WI.DeprecatedRemoteObjectProperty("set " + property.name, WI.RemoteObject.fromPayload(property.set, this._target), property));
689             } else
690                 result.push(new WI.DeprecatedRemoteObjectProperty(property.name, WI.RemoteObject.fromPayload(property.value, this._target), property));
691         }
692
693         callback(result);
694     }
695 };
696
697 WI.RemoteObject.FakeRemoteObjectId = "fake-remote-object";
698
699 WI.RemoteObject.SourceCodeLocationPromise = {
700     NoSourceFound: "remote-object-source-code-location-promise-no-source-found",
701     MissingObjectId: "remote-object-source-code-location-promise-missing-object-id"
702 };
703
704 // FIXME: Phase out this deprecated class.
705 WI.DeprecatedRemoteObjectProperty = class DeprecatedRemoteObjectProperty
706 {
707     constructor(name, value, descriptor)
708     {
709         this.name = name;
710         this.value = value;
711         this.enumerable = descriptor ? !!descriptor.enumerable : true;
712         this.writable = descriptor ? !!descriptor.writable : true;
713         if (descriptor && descriptor.wasThrown)
714             this.wasThrown = true;
715     }
716
717     // Static
718
719     fromPrimitiveValue(name, value)
720     {
721         return new WI.DeprecatedRemoteObjectProperty(name, WI.RemoteObject.fromPrimitiveValue(value));
722     }
723 };