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