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
31WebInspector.RemoteObject = function(objectId, type, subtype, value, description)
32{
33    this._type = type;
34    this._subtype = subtype;
35    if (objectId) {
36        // handle
37        this._objectId = objectId;
38        this._description = description;
39        this._hasChildren = true;
40    } else {
41        // Primitive or null object.
42        console.assert(type !== "object" || value === null);
43        this._description = description || (value + "");
44        this._hasChildren = false;
45        this.value = value;
46    }
47}
48
49WebInspector.RemoteObject.fromPrimitiveValue = function(value)
50{
51    return new WebInspector.RemoteObject(undefined, typeof value, undefined, value);
52}
53
54WebInspector.RemoteObject.fromLocalObject = function(value)
55{
56    return new WebInspector.LocalJSONObject(value);
57}
58
59WebInspector.RemoteObject.resolveNode = function(node, objectGroup, callback)
60{
61    function mycallback(error, object)
62    {
63        if (!callback)
64            return;
65
66        if (error || !object)
67            callback(null);
68        else
69            callback(WebInspector.RemoteObject.fromPayload(object));
70    }
71    DOMAgent.resolveNode(node.id, objectGroup, mycallback);
72}
73
74WebInspector.RemoteObject.fromPayload = function(payload)
75{
76    console.assert(typeof payload === "object", "Remote object payload should only be an object");
77
78    return new WebInspector.RemoteObject(payload.objectId, payload.type, payload.subtype, payload.value, payload.description);
79}
80
81WebInspector.RemoteObject.type = function(remoteObject)
82{
83    if (remoteObject === null)
84        return "null";
85
86    var type = typeof remoteObject;
87    if (type !== "object" && type !== "function")
88        return type;
89
90    return remoteObject.type;
91}
92
93WebInspector.RemoteObject.prototype = {
94    get objectId()
95    {
96        return this._objectId;
97    },
98
99    get type()
100    {
101        return this._type;
102    },
103
104    get subtype()
105    {
106        return this._subtype;
107    },
108
109    get description()
110    {
111        return this._description;
112    },
113
114    get hasChildren()
115    {
116        return this._hasChildren;
117    },
118
119    getOwnProperties: function(callback)
120    {
121        this._getProperties(true, callback);
122    },
123
124    getAllProperties: function(callback)
125    {
126        this._getProperties(false, callback);
127    },
128
129    _getProperties: function(ownProperties, callback)
130    {
131        if (!this._objectId) {
132            callback([]);
133            return;
134        }
135
136        function remoteObjectBinder(error, properties)
137        {
138            if (error) {
139                callback(null);
140                return;
141            }
142            var result = [];
143            for (var i = 0; properties && i < properties.length; ++i) {
144                var property = properties[i];
145                if (property.get || property.set) {
146                    if (property.get)
147                        result.push(new WebInspector.RemoteObjectProperty("get " + property.name, WebInspector.RemoteObject.fromPayload(property.get), property));
148                    if (property.set)
149                        result.push(new WebInspector.RemoteObjectProperty("set " + property.name, WebInspector.RemoteObject.fromPayload(property.set), property));
150                } else
151                    result.push(new WebInspector.RemoteObjectProperty(property.name, WebInspector.RemoteObject.fromPayload(property.value), property));
152            }
153            callback(result);
154        }
155        RuntimeAgent.getProperties(this._objectId, ownProperties, remoteObjectBinder);
156    },
157
158    setPropertyValue: function(name, value, callback)
159    {
160        if (!this._objectId) {
161            callback("Can't set a property of non-object.");
162            return;
163        }
164
165        RuntimeAgent.evaluate.invoke({expression:value, doNotPauseOnExceptionsAndMuteConsole:true}, evaluatedCallback.bind(this));
166
167        function evaluatedCallback(error, result, wasThrown)
168        {
169            if (error || wasThrown) {
170                callback(error || result.description);
171                return;
172            }
173
174            function setPropertyValue(propertyName, propertyValue)
175            {
176                this[propertyName] = propertyValue;
177            }
178
179            delete result.description; // Optimize on traffic.
180            RuntimeAgent.callFunctionOn(this._objectId, setPropertyValue.toString(), [{ value:name }, result], true, undefined, propertySetCallback.bind(this));
181            if (result._objectId)
182                RuntimeAgent.releaseObject(result._objectId);
183        }
184
185        function propertySetCallback(error, result, wasThrown)
186        {
187            if (error || wasThrown) {
188                callback(error || result.description);
189                return;
190            }
191            callback();
192        }
193    },
194
195    pushNodeToFrontend: function(callback)
196    {
197        if (this._objectId)
198            WebInspector.domTreeManager.pushNodeToFrontend(this._objectId, callback);
199        else
200            callback(0);
201    },
202
203    callFunction: function(functionDeclaration, args, callback)
204    {
205        function mycallback(error, result, wasThrown)
206        {
207            callback((error || wasThrown) ? null : WebInspector.RemoteObject.fromPayload(result));
208        }
209
210        RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, undefined, mycallback);
211    },
212
213    callFunctionJSON: function(functionDeclaration, args, callback)
214    {
215        function mycallback(error, result, wasThrown)
216        {
217            callback((error || wasThrown) ? null : result.value);
218        }
219
220        RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, true, mycallback);
221    },
222
223    release: function()
224    {
225        RuntimeAgent.releaseObject(this._objectId);
226    },
227
228    arrayLength: function()
229    {
230        if (this.subtype !== "array")
231            return 0;
232
233        var matches = this._description.match(/\[([0-9]+)\]/);
234        if (!matches)
235            return 0;
236        return parseInt(matches[1], 10);
237    }
238}
239
240WebInspector.RemoteObjectProperty = function(name, value, descriptor)
241{
242    this.name = name;
243    this.value = value;
244    this.enumerable = descriptor ? !!descriptor.enumerable : true;
245    this.writable = descriptor ? !!descriptor.writable : true;
246    if (descriptor && descriptor.wasThrown)
247        this.wasThrown = true;
248}
249
250WebInspector.RemoteObjectProperty.fromPrimitiveValue = function(name, value)
251{
252    return new WebInspector.RemoteObjectProperty(name, WebInspector.RemoteObject.fromPrimitiveValue(value));
253}
254
255// The below is a wrapper around a local object that provides an interface comaptible
256// with RemoteObject, to be used by the UI code (primarily ObjectPropertiesSection).
257// Note that only JSON-compliant objects are currently supported, as there's no provision
258// for traversing prototypes, extracting class names via constuctor, handling properties
259// or functions.
260
261WebInspector.LocalJSONObject = function(value)
262{
263    this._value = value;
264}
265
266WebInspector.LocalJSONObject.prototype = {
267    get description()
268    {
269        if (this._cachedDescription)
270            return this._cachedDescription;
271
272        if (this.type === "object") {
273            switch (this.subtype) {
274            case "array":
275                function formatArrayItem(property)
276                {
277                    return property.value.description;
278                }
279                this._cachedDescription = this._concatenate("[", "]", formatArrayItem);
280                break;
281            case "null":
282                this._cachedDescription = "null";
283                break;
284            default:
285                function formatObjectItem(property)
286                {
287                    return property.name + ":" + property.value.description;
288                }
289                this._cachedDescription = this._concatenate("{", "}", formatObjectItem);
290            }
291        } else
292            this._cachedDescription = String(this._value);
293
294        return this._cachedDescription;
295    },
296
297    _concatenate: function(prefix, suffix, formatProperty)
298    {
299        const previewChars = 100;
300
301        var buffer = prefix;
302        var children = this._children();
303        for (var i = 0; i < children.length; ++i) {
304            var itemDescription = formatProperty(children[i]);
305            if (buffer.length + itemDescription.length > previewChars) {
306                buffer += ",\u2026";
307                break;
308            }
309            if (i)
310                buffer += ", ";
311            buffer += itemDescription;
312        }
313        buffer += suffix;
314        return buffer;
315    },
316
317    get type()
318    {
319        return typeof this._value;
320    },
321
322    get subtype()
323    {
324        if (this._value === null)
325            return "null";
326
327        if (this._value instanceof Array)
328            return "array";
329
330        return undefined;
331    },
332
333    get hasChildren()
334    {
335        return typeof this._value === "object" && this._value !== null && !isEmptyObject(this._value);
336    },
337
338    getOwnProperties: function(callback)
339    {
340        callback(this._children());
341    },
342
343    getAllProperties: function(callback)
344    {
345        callback(this._children());
346    },
347
348    _children: function()
349    {
350        if (!this.hasChildren)
351            return [];
352
353        function buildProperty(propName)
354        {
355            return new WebInspector.RemoteObjectProperty(propName, new WebInspector.LocalJSONObject(this._value[propName]));
356        }
357        if (!this._cachedChildren)
358            this._cachedChildren = Object.keys(this._value || {}).map(buildProperty.bind(this));
359        return this._cachedChildren;
360    },
361
362    isError: function()
363    {
364        return false;
365    }
366}
367