1/* 2 * Copyright (C) 2007 Apple 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 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29(function (InjectedScriptHost, inspectedWindow, injectedScriptId) { 30 31function bind(thisObject, memberFunction) 32{ 33 var func = memberFunction; 34 var args = Array.prototype.slice.call(arguments, 2); 35 function bound() 36 { 37 return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))); 38 } 39 bound.toString = function() { 40 return "bound: " + func; 41 }; 42 return bound; 43} 44 45var InjectedScript = function() 46{ 47 this._lastBoundObjectId = 1; 48 this._idToWrappedObject = {}; 49 this._idToObjectGroupName = {}; 50 this._objectGroups = {}; 51} 52 53InjectedScript.prototype = { 54 wrapObject: function(object, groupName, canAccessInspectedWindow) 55 { 56 if (canAccessInspectedWindow) 57 return this._wrapObject(object, groupName); 58 var result = {}; 59 result.type = typeof object; 60 result.description = this._toString(object); 61 return result; 62 }, 63 64 inspectNode: function(object) 65 { 66 this._inspect(object); 67 }, 68 69 _inspect: function(object) 70 { 71 if (arguments.length === 0) 72 return; 73 74 var objectId = this._wrapObject(object, ""); 75 var hints = {}; 76 77 switch (injectedScript._describe(object)) { 78 case "Database": 79 var databaseId = InjectedScriptHost.databaseId(object) 80 if (databaseId) 81 hints.databaseId = databaseId; 82 break; 83 case "Storage": 84 var storageId = InjectedScriptHost.storageId(object) 85 if (storageId) 86 hints.domStorageId = storageId; 87 break; 88 } 89 InjectedScriptHost.inspect(objectId, hints); 90 return object; 91 }, 92 93 _wrapObject: function(object, objectGroupName) 94 { 95 try { 96 if (typeof object === "object" || typeof object === "function" || this._isHTMLAllCollection(object)) { 97 var id = this._lastBoundObjectId++; 98 this._idToWrappedObject[id] = object; 99 var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}"; 100 if (objectGroupName) { 101 var group = this._objectGroups[objectGroupName]; 102 if (!group) { 103 group = []; 104 this._objectGroups[objectGroupName] = group; 105 } 106 group.push(id); 107 this._idToObjectGroupName[id] = objectGroupName; 108 } 109 } 110 return InjectedScript.RemoteObject.fromObject(object, objectId); 111 } catch (e) { 112 return InjectedScript.RemoteObject.fromObject("[ Exception: " + e.toString() + " ]"); 113 } 114 }, 115 116 _parseObjectId: function(objectId) 117 { 118 return eval("(" + objectId + ")"); 119 }, 120 121 releaseObjectGroup: function(objectGroupName) 122 { 123 var group = this._objectGroups[objectGroupName]; 124 if (!group) 125 return; 126 for (var i = 0; i < group.length; i++) 127 this._releaseObject(group[i]); 128 delete this._objectGroups[objectGroupName]; 129 }, 130 131 dispatch: function(methodName, args) 132 { 133 var argsArray = eval("(" + args + ")"); 134 var result = this[methodName].apply(this, argsArray); 135 if (typeof result === "undefined") { 136 inspectedWindow.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName); 137 result = null; 138 } 139 return result; 140 }, 141 142 getProperties: function(objectId, ignoreHasOwnProperty) 143 { 144 var parsedObjectId = this._parseObjectId(objectId); 145 var object = this._objectForId(parsedObjectId); 146 var objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; 147 148 if (!this._isDefined(object)) 149 return false; 150 var properties = []; 151 152 var propertyNames = ignoreHasOwnProperty ? this._getPropertyNames(object) : Object.getOwnPropertyNames(object); 153 if (!ignoreHasOwnProperty && object.__proto__) 154 propertyNames.push("__proto__"); 155 156 // Go over properties, prepare results. 157 for (var i = 0; i < propertyNames.length; ++i) { 158 var propertyName = propertyNames[i]; 159 160 var property = {}; 161 property.name = propertyName + ""; 162 var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName); 163 if (!isGetter) { 164 try { 165 property.value = this._wrapObject(object[propertyName], objectGroupName); 166 } catch(e) { 167 property.value = new InjectedScript.RemoteObject.fromException(e); 168 } 169 } else { 170 // FIXME: this should show something like "getter" (bug 16734). 171 property.value = new InjectedScript.RemoteObject.fromObject("\u2014"); // em dash 172 property.isGetter = true; 173 } 174 properties.push(property); 175 } 176 return properties; 177 }, 178 179 setPropertyValue: function(objectId, propertyName, expression) 180 { 181 var parsedObjectId = this._parseObjectId(objectId); 182 var object = this._objectForId(parsedObjectId); 183 if (!this._isDefined(object)) 184 return "Object with given id not found"; 185 186 var expressionLength = expression.length; 187 if (!expressionLength) { 188 delete object[propertyName]; 189 return propertyName in object ? "Cound not delete property." : undefined; 190 } 191 192 try { 193 // Surround the expression in parenthesis so the result of the eval is the result 194 // of the whole expression not the last potential sub-expression. 195 196 // There is a regression introduced here: eval is now happening against global object, 197 // not call frame while on a breakpoint. 198 // TODO: bring evaluation against call frame back. 199 var result = inspectedWindow.eval("(" + expression + ")"); 200 // Store the result in the property. 201 object[propertyName] = result; 202 } catch(e) { 203 try { 204 var result = inspectedWindow.eval("\"" + expression.replace(/"/g, "\\\"") + "\""); 205 object[propertyName] = result; 206 } catch(e) { 207 return e.toString(); 208 } 209 } 210 }, 211 212 releaseObject: function(objectId) 213 { 214 var parsedObjectId = this._parseObjectId(objectId); 215 this._releaseObject(parsedObjectId.id); 216 }, 217 218 _releaseObject: function(id) 219 { 220 delete this._idToWrappedObject[id]; 221 delete this._idToObjectGroupName[id]; 222 }, 223 224 _populatePropertyNames: function(object, resultSet) 225 { 226 for (var o = object; o; o = o.__proto__) { 227 try { 228 var names = Object.getOwnPropertyNames(o); 229 for (var i = 0; i < names.length; ++i) 230 resultSet[names[i]] = true; 231 } catch (e) { 232 } 233 } 234 }, 235 236 _getPropertyNames: function(object, resultSet) 237 { 238 var propertyNameSet = {}; 239 this._populatePropertyNames(object, propertyNameSet); 240 return Object.keys(propertyNameSet); 241 }, 242 243 evaluate: function(expression, objectGroup, injectCommandLineAPI) 244 { 245 return this._evaluateAndWrap(inspectedWindow.eval, inspectedWindow, expression, objectGroup, false, injectCommandLineAPI); 246 }, 247 248 evaluateOn: function(objectId, expression) 249 { 250 var parsedObjectId = this._parseObjectId(objectId); 251 var object = this._objectForId(parsedObjectId); 252 if (!object) 253 return "Could not find object with given id"; 254 try { 255 inspectedWindow.console._objectToEvaluateOn = object; 256 return this._evaluateAndWrap(inspectedWindow.eval, inspectedWindow, "(function() {" + expression + "}).call(window.console._objectToEvaluateOn)", parsedObjectId.objectGroup, false, false); 257 } finally { 258 delete inspectedWindow.console._objectToEvaluateOn; 259 } 260 }, 261 262 _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI) 263 { 264 try { 265 return this._wrapObject(this._evaluateOn(evalFunction, object, expression, isEvalOnCallFrame, injectCommandLineAPI), objectGroup); 266 } catch (e) { 267 return InjectedScript.RemoteObject.fromException(e); 268 } 269 }, 270 271 _evaluateOn: function(evalFunction, object, expression, isEvalOnCallFrame, injectCommandLineAPI) 272 { 273 // Only install command line api object for the time of evaluation. 274 // Surround the expression in with statements to inject our command line API so that 275 // the window object properties still take more precedent than our API functions. 276 277 try { 278 if (injectCommandLineAPI && inspectedWindow.console) { 279 inspectedWindow.console._commandLineAPI = new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null); 280 expression = "with ((window && window.console && window.console._commandLineAPI) || {}) {\n" + expression + "\n}"; 281 } 282 283 var value = evalFunction.call(object, expression); 284 285 // When evaluating on call frame error is not thrown, but returned as a value. 286 if (this._type(value) === "error") 287 throw value.toString(); 288 289 return value; 290 } finally { 291 if (injectCommandLineAPI && inspectedWindow.console) 292 delete inspectedWindow.console._commandLineAPI; 293 } 294 }, 295 296 callFrames: function() 297 { 298 var callFrame = InjectedScriptHost.currentCallFrame(); 299 if (!callFrame) 300 return false; 301 302 var result = []; 303 var depth = 0; 304 do { 305 result.push(new InjectedScript.CallFrameProxy(depth++, callFrame)); 306 callFrame = callFrame.caller; 307 } while (callFrame); 308 return result; 309 }, 310 311 evaluateOnCallFrame: function(callFrameId, expression, objectGroup, injectCommandLineAPI) 312 { 313 var callFrame = this._callFrameForId(callFrameId); 314 if (!callFrame) 315 return "Could not find call frame with given id"; 316 return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI); 317 }, 318 319 _callFrameForId: function(callFrameId) 320 { 321 var parsedCallFrameId = eval("(" + callFrameId + ")"); 322 var ordinal = parsedCallFrameId.ordinal; 323 var callFrame = InjectedScriptHost.currentCallFrame(); 324 while (--ordinal >= 0 && callFrame) 325 callFrame = callFrame.caller; 326 return callFrame; 327 }, 328 329 _objectForId: function(objectId) 330 { 331 return this._idToWrappedObject[objectId.id]; 332 }, 333 334 nodeForObjectId: function(objectId) 335 { 336 var parsedObjectId = this._parseObjectId(objectId); 337 var object = this._objectForId(parsedObjectId); 338 if (!object || this._type(object) !== "node") 339 return null; 340 return object; 341 }, 342 343 _isDefined: function(object) 344 { 345 return object || this._isHTMLAllCollection(object); 346 }, 347 348 _isHTMLAllCollection: function(object) 349 { 350 // document.all is reported as undefined, but we still want to process it. 351 return (typeof object === "undefined") && inspectedWindow.HTMLAllCollection && object instanceof inspectedWindow.HTMLAllCollection; 352 }, 353 354 _type: function(obj) 355 { 356 if (obj === null) 357 return "null"; 358 359 var type = typeof obj; 360 if (type !== "object" && type !== "function") { 361 // FIXME(33716): typeof document.all is always 'undefined'. 362 if (this._isHTMLAllCollection(obj)) 363 return "array"; 364 return type; 365 } 366 367 // If owning frame has navigated to somewhere else window properties will be undefined. 368 // In this case just return result of the typeof. 369 if (!inspectedWindow.document) 370 return type; 371 372 if (obj instanceof inspectedWindow.Node) 373 return (obj.nodeType === undefined ? type : "node"); 374 if (obj instanceof inspectedWindow.String) 375 return "string"; 376 if (obj instanceof inspectedWindow.Array) 377 return "array"; 378 if (obj instanceof inspectedWindow.Boolean) 379 return "boolean"; 380 if (obj instanceof inspectedWindow.Number) 381 return "number"; 382 if (obj instanceof inspectedWindow.Date) 383 return "date"; 384 if (obj instanceof inspectedWindow.RegExp) 385 return "regexp"; 386 // FireBug's array detection. 387 try { 388 if (isFinite(obj.length) && typeof obj.splice === "function") 389 return "array"; 390 if (isFinite(obj.length) && typeof obj.callee === "function") // arguments. 391 return "array"; 392 } catch (e) { 393 return type; 394 } 395 if (obj instanceof inspectedWindow.NodeList) 396 return "array"; 397 if (obj instanceof inspectedWindow.HTMLCollection) 398 return "array"; 399 if (obj instanceof inspectedWindow.Error) 400 return "error"; 401 return type; 402 }, 403 404 _describe: function(obj) 405 { 406 var type = this._type(obj); 407 408 switch (type) { 409 case "object": 410 // Fall through. 411 case "node": 412 var result = InjectedScriptHost.internalConstructorName(obj); 413 if (result === "Object") { 414 // In Chromium DOM wrapper prototypes will have Object as their constructor name, 415 // get the real DOM wrapper name from the constructor property. 416 var constructorName = obj.constructor && obj.constructor.name; 417 if (constructorName) 418 return constructorName; 419 } 420 return result; 421 case "array": 422 var className = InjectedScriptHost.internalConstructorName(obj); 423 if (typeof obj.length === "number") 424 className += "[" + obj.length + "]"; 425 return className; 426 case "string": 427 return obj; 428 case "function": 429 // Fall through. 430 default: 431 return this._toString(obj); 432 } 433 }, 434 435 _toString: function(obj) 436 { 437 // We don't use String(obj) because inspectedWindow.String is undefined if owning frame navigated to another page. 438 return "" + obj; 439 } 440} 441 442var injectedScript = new InjectedScript(); 443 444InjectedScript.RemoteObject = function(objectId, type, description, hasChildren) 445{ 446 if (objectId) { 447 this.objectId = objectId; 448 this.hasChildren = hasChildren; 449 } 450 this.type = type; 451 this.description = description; 452} 453 454InjectedScript.RemoteObject.fromException = function(e) 455{ 456 return new InjectedScript.RemoteObject(null, "error", e.toString()); 457} 458 459InjectedScript.RemoteObject.fromObject = function(object, objectId) 460{ 461 var type = injectedScript._type(object); 462 var rawType = typeof object; 463 var hasChildren = (rawType === "object" && object !== null && (!!Object.getOwnPropertyNames(object).length || !!object.__proto__)) || rawType === "function"; 464 var description = ""; 465 try { 466 var description = injectedScript._describe(object); 467 return new InjectedScript.RemoteObject(objectId, type, description, hasChildren); 468 } catch (e) { 469 return InjectedScript.RemoteObject.fromException(e); 470 } 471} 472 473InjectedScript.CallFrameProxy = function(ordinal, callFrame) 474{ 475 this.id = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}"; 476 this.functionName = (callFrame.type === "function" ? callFrame.functionName : ""); 477 this.location = { sourceID: callFrame.sourceID, lineNumber: callFrame.line, columnNumber: callFrame.column }; 478 this.scopeChain = this._wrapScopeChain(callFrame); 479} 480 481InjectedScript.CallFrameProxy.prototype = { 482 _wrapScopeChain: function(callFrame) 483 { 484 const GLOBAL_SCOPE = 0; 485 const LOCAL_SCOPE = 1; 486 const WITH_SCOPE = 2; 487 const CLOSURE_SCOPE = 3; 488 const CATCH_SCOPE = 4; 489 490 var scopeTypeNames = {}; 491 scopeTypeNames[GLOBAL_SCOPE] = "global"; 492 scopeTypeNames[LOCAL_SCOPE] = "local"; 493 scopeTypeNames[WITH_SCOPE] = "with"; 494 scopeTypeNames[CLOSURE_SCOPE] = "closure"; 495 scopeTypeNames[CATCH_SCOPE] = "catch"; 496 497 var scopeChain = callFrame.scopeChain; 498 var scopeChainProxy = []; 499 var foundLocalScope = false; 500 for (var i = 0; i < scopeChain.length; i++) { 501 var scope = {}; 502 scope.object = injectedScript._wrapObject(scopeChain[i], "backtrace"); 503 504 var scopeType = callFrame.scopeType(i); 505 scope.type = scopeTypeNames[scopeType]; 506 507 if (scopeType === LOCAL_SCOPE) 508 scope.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace"); 509 510 scopeChainProxy.push(scope); 511 } 512 return scopeChainProxy; 513 } 514} 515 516function CommandLineAPI(commandLineAPIImpl, callFrame) 517{ 518 function inScopeVariables(member) 519 { 520 if (!callFrame) 521 return false; 522 523 var scopeChain = callFrame.scopeChain; 524 for (var i = 0; i < scopeChain.length; ++i) { 525 if (member in scopeChain[i]) 526 return true; 527 } 528 return false; 529 } 530 531 for (var i = 0; i < CommandLineAPI.members_.length; ++i) { 532 var member = CommandLineAPI.members_[i]; 533 if (member in inspectedWindow || inScopeVariables(member)) 534 continue; 535 536 this[member] = bind(commandLineAPIImpl, commandLineAPIImpl[member]); 537 } 538 539 for (var i = 0; i < 5; ++i) { 540 var member = "$" + i; 541 if (member in inspectedWindow || inScopeVariables(member)) 542 continue; 543 544 this.__defineGetter__("$" + i, bind(commandLineAPIImpl, commandLineAPIImpl._inspectedNode, i)); 545 } 546} 547 548CommandLineAPI.members_ = [ 549 "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd", 550 "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear" 551]; 552 553function CommandLineAPIImpl() 554{ 555} 556 557CommandLineAPIImpl.prototype = { 558 $: function() 559 { 560 return document.getElementById.apply(document, arguments) 561 }, 562 563 $$: function() 564 { 565 return document.querySelectorAll.apply(document, arguments) 566 }, 567 568 $x: function(xpath, context) 569 { 570 var nodes = []; 571 try { 572 var doc = (context && context.ownerDocument) || inspectedWindow.document; 573 var results = doc.evaluate(xpath, context || doc, null, XPathResult.ANY_TYPE, null); 574 var node; 575 while (node = results.iterateNext()) 576 nodes.push(node); 577 } catch (e) { 578 } 579 return nodes; 580 }, 581 582 dir: function() 583 { 584 return console.dir.apply(console, arguments) 585 }, 586 587 dirxml: function() 588 { 589 return console.dirxml.apply(console, arguments) 590 }, 591 592 keys: function(object) 593 { 594 return Object.keys(object); 595 }, 596 597 values: function(object) 598 { 599 var result = []; 600 for (var key in object) 601 result.push(object[key]); 602 return result; 603 }, 604 605 profile: function() 606 { 607 return console.profile.apply(console, arguments) 608 }, 609 610 profileEnd: function() 611 { 612 return console.profileEnd.apply(console, arguments) 613 }, 614 615 monitorEvents: function(object, types) 616 { 617 if (!object || !object.addEventListener || !object.removeEventListener) 618 return; 619 types = this._normalizeEventTypes(types); 620 for (var i = 0; i < types.length; ++i) { 621 object.removeEventListener(types[i], this._logEvent, false); 622 object.addEventListener(types[i], this._logEvent, false); 623 } 624 }, 625 626 unmonitorEvents: function(object, types) 627 { 628 if (!object || !object.addEventListener || !object.removeEventListener) 629 return; 630 types = this._normalizeEventTypes(types); 631 for (var i = 0; i < types.length; ++i) 632 object.removeEventListener(types[i], this._logEvent, false); 633 }, 634 635 inspect: function(object) 636 { 637 return injectedScript._inspect(object); 638 }, 639 640 copy: function(object) 641 { 642 if (injectedScript._type(object) === "node") 643 object = object.outerHTML; 644 InjectedScriptHost.copyText(object); 645 }, 646 647 clear: function() 648 { 649 InjectedScriptHost.clearConsoleMessages(); 650 }, 651 652 _inspectedNode: function(num) 653 { 654 return InjectedScriptHost.inspectedNode(num); 655 }, 656 657 _normalizeEventTypes: function(types) 658 { 659 if (typeof types === "undefined") 660 types = [ "mouse", "key", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll" ]; 661 else if (typeof types === "string") 662 types = [ types ]; 663 664 var result = []; 665 for (var i = 0; i < types.length; i++) { 666 if (types[i] === "mouse") 667 result.splice(0, 0, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout"); 668 else if (types[i] === "key") 669 result.splice(0, 0, "keydown", "keyup", "keypress"); 670 else 671 result.push(types[i]); 672 } 673 return result; 674 }, 675 676 _logEvent: function(event) 677 { 678 console.log(event.type, event); 679 } 680} 681 682injectedScript._commandLineAPIImpl = new CommandLineAPIImpl(); 683return injectedScript; 684}) 685