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