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/** 30 * @param {InjectedScriptHost} InjectedScriptHost 31 * @param {Window} inspectedWindow 32 * @param {number} injectedScriptId 33 */ 34(function (InjectedScriptHost, inspectedWindow, injectedScriptId) { 35 36// Protect against Object overwritten by the user code. 37var Object = {}.constructor; 38 39/** 40 * @param {Arguments} array 41 * @param {number=} index 42 * @return {Array.<*>} 43 */ 44function slice(array, index) 45{ 46 var result = []; 47 for (var i = index || 0; i < array.length; ++i) 48 result.push(array[i]); 49 return result; 50} 51 52/** 53 * Please use this bind, not the one from Function.prototype 54 * @param {function(...)} func 55 * @param {Object} thisObject 56 * @param {...number} var_args 57 */ 58function bind(func, thisObject, var_args) 59{ 60 var args = slice(arguments, 2); 61 62 /** 63 * @param {...number} var_args 64 */ 65 function bound(var_args) 66 { 67 return func.apply(thisObject, args.concat(slice(arguments))); 68 } 69 bound.toString = function() { 70 return "bound: " + func; 71 }; 72 return bound; 73} 74 75/** 76 * @constructor 77 */ 78var InjectedScript = function() 79{ 80 this._lastBoundObjectId = 1; 81 this._idToWrappedObject = {}; 82 this._idToObjectGroupName = {}; 83 this._objectGroups = {}; 84 this._modules = {}; 85} 86 87/** 88 * @type {Object.<string, boolean>} 89 * @const 90 */ 91InjectedScript.primitiveTypes = { 92 undefined: true, 93 boolean: true, 94 number: true, 95 string: true 96} 97 98InjectedScript.prototype = { 99 /** 100 * @param {*} object 101 * @return {boolean} 102 */ 103 isPrimitiveValue: function(object) 104 { 105 // FIXME(33716): typeof document.all is always 'undefined'. 106 return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object); 107 }, 108 109 /** 110 * @param {*} object 111 * @param {string} groupName 112 * @param {boolean} canAccessInspectedWindow 113 * @param {boolean} generatePreview 114 * @return {!RuntimeAgent.RemoteObject} 115 */ 116 wrapObject: function(object, groupName, canAccessInspectedWindow, generatePreview) 117 { 118 if (canAccessInspectedWindow) 119 return this._wrapObject(object, groupName, false, generatePreview); 120 return this._fallbackWrapper(object); 121 }, 122 123 /** 124 * @param {*} object 125 * @return {!RuntimeAgent.RemoteObject} 126 */ 127 _fallbackWrapper: function(object) 128 { 129 var result = {}; 130 result.type = typeof object; 131 if (this.isPrimitiveValue(object)) 132 result.value = object; 133 else 134 result.description = this._toString(object); 135 return /** @type {!RuntimeAgent.RemoteObject} */ (result); 136 }, 137 138 /** 139 * @param {boolean} canAccessInspectedWindow 140 * @param {Object} table 141 * @param {Array.<string>|string|boolean} columns 142 * @return {!RuntimeAgent.RemoteObject} 143 */ 144 wrapTable: function(canAccessInspectedWindow, table, columns) 145 { 146 if (!canAccessInspectedWindow) 147 return this._fallbackWrapper(table); 148 var columnNames = null; 149 if (typeof columns === "string") 150 columns = [columns]; 151 if (InjectedScriptHost.type(columns) == "array") { 152 columnNames = []; 153 for (var i = 0; i < columns.length; ++i) 154 columnNames.push(String(columns[i])); 155 } 156 return this._wrapObject(table, "console", false, true, columnNames); 157 }, 158 159 /** 160 * @param {*} object 161 */ 162 inspectNode: function(object) 163 { 164 this._inspect(object); 165 }, 166 167 /** 168 * @param {*} object 169 * @return {*} 170 */ 171 _inspect: function(object) 172 { 173 if (arguments.length === 0) 174 return; 175 176 var objectId = this._wrapObject(object, ""); 177 var hints = {}; 178 179 switch (injectedScript._describe(object)) { 180 case "Database": 181 var databaseId = InjectedScriptHost.databaseId(object) 182 if (databaseId) 183 hints.databaseId = databaseId; 184 break; 185 case "Storage": 186 var storageId = InjectedScriptHost.storageId(object) 187 if (storageId) 188 hints.domStorageId = InjectedScriptHost.evaluate("(" + storageId + ")"); 189 break; 190 } 191 InjectedScriptHost.inspect(objectId, hints); 192 return object; 193 }, 194 195 /** 196 * This method cannot throw. 197 * @param {*} object 198 * @param {string=} objectGroupName 199 * @param {boolean=} forceValueType 200 * @param {boolean=} generatePreview 201 * @param {?Array.<string>=} columnNames 202 * @return {!RuntimeAgent.RemoteObject} 203 * @suppress {checkTypes} 204 */ 205 _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames) 206 { 207 try { 208 return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType, generatePreview, columnNames); 209 } catch (e) { 210 try { 211 var description = injectedScript._describe(e); 212 } catch (ex) { 213 var description = "<failed to convert exception to string>"; 214 } 215 return new InjectedScript.RemoteObject(description); 216 } 217 }, 218 219 /** 220 * @param {*} object 221 * @param {string=} objectGroupName 222 * @return {string} 223 */ 224 _bind: function(object, objectGroupName) 225 { 226 var id = this._lastBoundObjectId++; 227 this._idToWrappedObject[id] = object; 228 var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}"; 229 if (objectGroupName) { 230 var group = this._objectGroups[objectGroupName]; 231 if (!group) { 232 group = []; 233 this._objectGroups[objectGroupName] = group; 234 } 235 group.push(id); 236 this._idToObjectGroupName[id] = objectGroupName; 237 } 238 return objectId; 239 }, 240 241 /** 242 * @param {string} objectId 243 * @return {Object} 244 */ 245 _parseObjectId: function(objectId) 246 { 247 return InjectedScriptHost.evaluate("(" + objectId + ")"); 248 }, 249 250 /** 251 * @param {string} objectGroupName 252 */ 253 releaseObjectGroup: function(objectGroupName) 254 { 255 var group = this._objectGroups[objectGroupName]; 256 if (!group) 257 return; 258 for (var i = 0; i < group.length; i++) 259 this._releaseObject(group[i]); 260 delete this._objectGroups[objectGroupName]; 261 }, 262 263 /** 264 * @param {string} methodName 265 * @param {string} args 266 * @return {*} 267 */ 268 dispatch: function(methodName, args) 269 { 270 var argsArray = InjectedScriptHost.evaluate("(" + args + ")"); 271 var result = this[methodName].apply(this, argsArray); 272 if (typeof result === "undefined") { 273 inspectedWindow.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName); 274 result = null; 275 } 276 return result; 277 }, 278 279 /** 280 * @param {string} objectId 281 * @param {boolean} ownProperties 282 * @return {Array.<RuntimeAgent.PropertyDescriptor>|boolean} 283 */ 284 getProperties: function(objectId, ownProperties) 285 { 286 var parsedObjectId = this._parseObjectId(objectId); 287 var object = this._objectForId(parsedObjectId); 288 var objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; 289 290 if (!this._isDefined(object)) 291 return false; 292 var descriptors = this._propertyDescriptors(object, ownProperties); 293 294 // Go over properties, wrap object values. 295 for (var i = 0; i < descriptors.length; ++i) { 296 var descriptor = descriptors[i]; 297 if ("get" in descriptor) 298 descriptor.get = this._wrapObject(descriptor.get, objectGroupName); 299 if ("set" in descriptor) 300 descriptor.set = this._wrapObject(descriptor.set, objectGroupName); 301 if ("value" in descriptor) 302 descriptor.value = this._wrapObject(descriptor.value, objectGroupName); 303 if (!("configurable" in descriptor)) 304 descriptor.configurable = false; 305 if (!("enumerable" in descriptor)) 306 descriptor.enumerable = false; 307 } 308 return descriptors; 309 }, 310 311 /** 312 * @param {string} objectId 313 * @return {Array.<Object>|boolean} 314 */ 315 getInternalProperties: function(objectId, ownProperties) 316 { 317 var parsedObjectId = this._parseObjectId(objectId); 318 var object = this._objectForId(parsedObjectId); 319 var objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; 320 if (!this._isDefined(object)) 321 return false; 322 var descriptors = []; 323 var internalProperties = InjectedScriptHost.getInternalProperties(object); 324 if (internalProperties) { 325 for (var i = 0; i < internalProperties.length; i++) { 326 var property = internalProperties[i]; 327 var descriptor = { 328 name: property.name, 329 value: this._wrapObject(property.value, objectGroupName) 330 }; 331 descriptors.push(descriptor); 332 } 333 } 334 return descriptors; 335 }, 336 337 /** 338 * @param {string} functionId 339 * @return {!DebuggerAgent.FunctionDetails|string} 340 */ 341 getFunctionDetails: function(functionId) 342 { 343 var parsedFunctionId = this._parseObjectId(functionId); 344 var func = this._objectForId(parsedFunctionId); 345 if (typeof func !== "function") 346 return "Cannot resolve function by id."; 347 var details = InjectedScriptHost.functionDetails(func); 348 if (!details) 349 return "Cannot resolve function details."; 350 if ("rawScopes" in details) { 351 var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id]; 352 var rawScopes = details.rawScopes; 353 var scopes = []; 354 delete details.rawScopes; 355 for (var i = 0; i < rawScopes.length; i++) 356 scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName)); 357 details.scopeChain = scopes; 358 } 359 return details; 360 }, 361 362 /** 363 * @param {string} objectId 364 */ 365 releaseObject: function(objectId) 366 { 367 var parsedObjectId = this._parseObjectId(objectId); 368 this._releaseObject(parsedObjectId.id); 369 }, 370 371 /** 372 * @param {string} id 373 */ 374 _releaseObject: function(id) 375 { 376 delete this._idToWrappedObject[id]; 377 delete this._idToObjectGroupName[id]; 378 }, 379 380 /** 381 * @param {Object} object 382 * @param {boolean} ownProperties 383 * @return {Array.<Object>} 384 */ 385 _propertyDescriptors: function(object, ownProperties) 386 { 387 var descriptors = []; 388 var nameProcessed = {}; 389 nameProcessed["__proto__"] = null; 390 for (var o = object; this._isDefined(o); o = o.__proto__) { 391 var names = Object.getOwnPropertyNames(/** @type {!Object} */ (o)); 392 for (var i = 0; i < names.length; ++i) { 393 var name = names[i]; 394 if (nameProcessed[name]) 395 continue; 396 397 try { 398 nameProcessed[name] = true; 399 var descriptor = Object.getOwnPropertyDescriptor(/** @type {!Object} */ (object), name); 400 if (!descriptor) { 401 // Not all bindings provide proper descriptors. Fall back to the writable, configurable property. 402 try { 403 descriptor = { name: name, value: object[name], writable: false, configurable: false, enumerable: false}; 404 if (o === object) 405 descriptor.isOwn = true; 406 descriptors.push(descriptor); 407 } catch (e) { 408 // Silent catch. 409 } 410 continue; 411 } 412 } catch (e) { 413 var descriptor = {}; 414 descriptor.value = e; 415 descriptor.wasThrown = true; 416 } 417 418 descriptor.name = name; 419 if (o === object) 420 descriptor.isOwn = true; 421 descriptors.push(descriptor); 422 } 423 if (ownProperties) { 424 if (object.__proto__) 425 descriptors.push({ name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true}); 426 break; 427 } 428 } 429 return descriptors; 430 }, 431 432 /** 433 * @param {string} expression 434 * @param {string} objectGroup 435 * @param {boolean} injectCommandLineAPI 436 * @param {boolean} returnByValue 437 * @param {boolean} generatePreview 438 * @return {*} 439 */ 440 evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview) 441 { 442 return this._evaluateAndWrap(InjectedScriptHost.evaluate, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview); 443 }, 444 445 /** 446 * @param {string} objectId 447 * @param {string} expression 448 * @param {boolean} returnByValue 449 * @return {Object|string} 450 */ 451 callFunctionOn: function(objectId, expression, args, returnByValue) 452 { 453 var parsedObjectId = this._parseObjectId(objectId); 454 var object = this._objectForId(parsedObjectId); 455 if (!this._isDefined(object)) 456 return "Could not find object with given id"; 457 458 if (args) { 459 var resolvedArgs = []; 460 args = InjectedScriptHost.evaluate(args); 461 for (var i = 0; i < args.length; ++i) { 462 var resolvedCallArgument; 463 try { 464 resolvedCallArgument = this._resolveCallArgument(args[i]); 465 } catch (e) { 466 return String(e); 467 } 468 resolvedArgs.push(resolvedCallArgument) 469 } 470 } 471 472 try { 473 var objectGroup = this._idToObjectGroupName[parsedObjectId.id]; 474 var func = InjectedScriptHost.evaluate("(" + expression + ")"); 475 if (typeof func !== "function") 476 return "Given expression does not evaluate to a function"; 477 478 return { wasThrown: false, 479 result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue) }; 480 } catch (e) { 481 return this._createThrownValue(e, objectGroup); 482 } 483 }, 484 485 /** 486 * Resolves a value from CallArgument description. 487 * @param {RuntimeAgent.CallArgument} callArgumentJson 488 * @return {*} resolved value 489 * @throws {string} error message 490 */ 491 _resolveCallArgument: function(callArgumentJson) { 492 var objectId = callArgumentJson.objectId; 493 if (objectId) { 494 var parsedArgId = this._parseObjectId(objectId); 495 if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId) 496 throw "Arguments should belong to the same JavaScript world as the target object."; 497 498 var resolvedArg = this._objectForId(parsedArgId); 499 if (!this._isDefined(resolvedArg)) 500 throw "Could not find object with given id"; 501 502 return resolvedArg; 503 } else if ("value" in callArgumentJson) 504 return callArgumentJson.value; 505 else 506 return undefined; 507 }, 508 509 /** 510 * @param {Function} evalFunction 511 * @param {Object} object 512 * @param {string} objectGroup 513 * @param {boolean} isEvalOnCallFrame 514 * @param {boolean} injectCommandLineAPI 515 * @param {boolean} returnByValue 516 * @param {boolean} generatePreview 517 * @return {*} 518 */ 519 _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview) 520 { 521 try { 522 return { wasThrown: false, 523 result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI), objectGroup, returnByValue, generatePreview) }; 524 } catch (e) { 525 return this._createThrownValue(e, objectGroup); 526 } 527 }, 528 529 /** 530 * @param {*} value 531 * @param {string} objectGroup 532 * @return {Object} 533 */ 534 _createThrownValue: function(value, objectGroup) 535 { 536 var remoteObject = this._wrapObject(value, objectGroup); 537 try { 538 remoteObject.description = this._toString(value); 539 } catch (e) {} 540 return { wasThrown: true, 541 result: remoteObject }; 542 }, 543 544 /** 545 * @param {Function} evalFunction 546 * @param {Object} object 547 * @param {string} objectGroup 548 * @param {string} expression 549 * @param {boolean} isEvalOnCallFrame 550 * @param {boolean} injectCommandLineAPI 551 * @return {*} 552 */ 553 _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI) 554 { 555 var commandLineAPI = injectCommandLineAPI ? new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null) : null; 556 557 if (isEvalOnCallFrame) { 558 // We can only use this approach if the evaluate function is the true 'eval'. That allows us to use it with 559 // the 'eval' identifier when calling it. Using 'eval' grants access to the local scope of the closure we 560 // create that provides the command line APIs. 561 562 var parameters = [InjectedScriptHost.evaluate, expression]; 563 var expressionFunctionBody = "var __originalEval = window.eval; window.eval = __eval; try { return eval(__currentExpression); } finally { window.eval = __originalEval; }"; 564 565 if (commandLineAPI) { 566 // To avoid using a 'with' statement (which fails in strict mode and requires injecting the API object) 567 // we instead create a closure where we evaluate the expression. The command line APIs are passed as 568 // parameters to the closure so they are in scope but not injected. This allows the code evaluated in 569 // the console to stay in strict mode (if is was already set), or to get strict mode by prefixing 570 // expressions with 'use strict';. 571 572 var parameterNames = Object.getOwnPropertyNames(commandLineAPI); 573 for (var i = 0; i < parameterNames.length; ++i) 574 parameters.push(commandLineAPI[parameterNames[i]]); 575 576 var expressionFunctionString = "(function(__eval, __currentExpression, " + parameterNames.join(", ") + ") { " + expressionFunctionBody + " })"; 577 } else { 578 // Use a closure in this case too to keep the same behavior of 'var' being captured by the closure instead 579 // of leaking out into the calling scope. 580 var expressionFunctionString = "(function(__eval, __currentExpression) { " + expressionFunctionBody + " })"; 581 } 582 583 // Bind 'this' to the function expression using another closure instead of Function.prototype.bind. This ensures things will work if the page replaces bind. 584 var boundExpressionFunctionString = "(function(__function, __thisObject) { return function() { return __function.apply(__thisObject, arguments) }; })(" + expressionFunctionString + ", this)"; 585 var expressionFunction = evalFunction.call(object, boundExpressionFunctionString); 586 var result = expressionFunction.apply(null, parameters); 587 588 if (objectGroup === "console") 589 this._lastResult = result; 590 591 return result; 592 } 593 594 // When not evaluating on a call frame we use a 'with' statement to allow var and function statements to leak 595 // into the global scope. This allow them to stick around between evaluations. 596 597 try { 598 if (commandLineAPI && inspectedWindow.console) { 599 inspectedWindow.console.__commandLineAPI = commandLineAPI; 600 expression = "with ((window && window.console && window.console.__commandLineAPI) || {}) { " + expression + "\n}"; 601 } 602 603 var result = evalFunction.call(object, expression); 604 605 if (objectGroup === "console") 606 this._lastResult = result; 607 608 return result; 609 } finally { 610 if (commandLineAPI && inspectedWindow.console) 611 delete inspectedWindow.console.__commandLineAPI; 612 } 613 }, 614 615 /** 616 * @param {Object} callFrame 617 * @return {Array.<InjectedScript.CallFrameProxy>|boolean} 618 */ 619 wrapCallFrames: function(callFrame) 620 { 621 if (!callFrame) 622 return false; 623 624 var result = []; 625 var depth = 0; 626 do { 627 result.push(new InjectedScript.CallFrameProxy(depth++, callFrame)); 628 callFrame = callFrame.caller; 629 } while (callFrame); 630 return result; 631 }, 632 633 /** 634 * @param {Object} topCallFrame 635 * @param {string} callFrameId 636 * @param {string} expression 637 * @param {string} objectGroup 638 * @param {boolean} injectCommandLineAPI 639 * @param {boolean} returnByValue 640 * @param {boolean} generatePreview 641 * @return {*} 642 */ 643 evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview) 644 { 645 var callFrame = this._callFrameForId(topCallFrame, callFrameId); 646 if (!callFrame) 647 return "Could not find call frame with given id"; 648 return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview); 649 }, 650 651 /** 652 * @param {Object} topCallFrame 653 * @param {string} callFrameId 654 * @return {*} 655 */ 656 restartFrame: function(topCallFrame, callFrameId) 657 { 658 var callFrame = this._callFrameForId(topCallFrame, callFrameId); 659 if (!callFrame) 660 return "Could not find call frame with given id"; 661 var result = callFrame.restart(); 662 if (result === false) 663 result = "Restart frame is not supported"; 664 return result; 665 }, 666 667 /** 668 * Either callFrameId or functionObjectId must be specified. 669 * @param {Object} topCallFrame 670 * @param {string|boolean} callFrameId or false 671 * @param {string|boolean} functionObjectId or false 672 * @param {number} scopeNumber 673 * @param {string} variableName 674 * @param {string} newValueJsonString RuntimeAgent.CallArgument structure serialized as string 675 * @return {string|undefined} undefined if success or an error message 676 */ 677 setVariableValue: function(topCallFrame, callFrameId, functionObjectId, scopeNumber, variableName, newValueJsonString) 678 { 679 var setter; 680 if (typeof callFrameId === "string") { 681 var callFrame = this._callFrameForId(topCallFrame, callFrameId); 682 if (!callFrame) 683 return "Could not find call frame with given id"; 684 setter = callFrame.setVariableValue.bind(callFrame); 685 } else { 686 var parsedFunctionId = this._parseObjectId(/** @type {string} */(functionObjectId)); 687 var func = this._objectForId(parsedFunctionId); 688 if (typeof func !== "function") 689 return "Cannot resolve function by id."; 690 setter = InjectedScriptHost.setFunctionVariableValue.bind(InjectedScriptHost, func); 691 } 692 var newValueJson; 693 try { 694 newValueJson = InjectedScriptHost.evaluate("(" + newValueJsonString + ")"); 695 } catch (e) { 696 return "Failed to parse new value JSON " + newValueJsonString + " : " + e; 697 } 698 var resolvedValue; 699 try { 700 resolvedValue = this._resolveCallArgument(newValueJson); 701 } catch (e) { 702 return String(e); 703 } 704 try { 705 setter(scopeNumber, variableName, resolvedValue); 706 } catch (e) { 707 return "Failed to change variable value: " + e; 708 } 709 return undefined; 710 }, 711 712 /** 713 * @param {Object} topCallFrame 714 * @param {string} callFrameId 715 * @return {Object} 716 */ 717 _callFrameForId: function(topCallFrame, callFrameId) 718 { 719 var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")"); 720 var ordinal = parsedCallFrameId["ordinal"]; 721 var callFrame = topCallFrame; 722 while (--ordinal >= 0 && callFrame) 723 callFrame = callFrame.caller; 724 return callFrame; 725 }, 726 727 /** 728 * @param {Object} objectId 729 * @return {Object} 730 */ 731 _objectForId: function(objectId) 732 { 733 return this._idToWrappedObject[objectId.id]; 734 }, 735 736 /** 737 * @param {string} objectId 738 * @return {Object} 739 */ 740 findObjectById: function(objectId) 741 { 742 var parsedObjectId = this._parseObjectId(objectId); 743 return this._objectForId(parsedObjectId); 744 }, 745 746 /** 747 * @param {string} objectId 748 * @return {Node} 749 */ 750 nodeForObjectId: function(objectId) 751 { 752 var object = this.findObjectById(objectId); 753 if (!object || this._subtype(object) !== "node") 754 return null; 755 return /** @type {Node} */ (object); 756 }, 757 758 /** 759 * @param {string} name 760 * @return {Object} 761 */ 762 module: function(name) 763 { 764 return this._modules[name]; 765 }, 766 767 /** 768 * @param {string} name 769 * @param {string} source 770 * @return {Object} 771 */ 772 injectModule: function(name, source) 773 { 774 delete this._modules[name]; 775 var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")"); 776 if (typeof moduleFunction !== "function") { 777 inspectedWindow.console.error("Web Inspector error: A function was expected for module %s evaluation", name); 778 return null; 779 } 780 var module = moduleFunction.call(inspectedWindow, InjectedScriptHost, inspectedWindow, injectedScriptId); 781 this._modules[name] = module; 782 return module; 783 }, 784 785 /** 786 * @param {*} object 787 * @return {boolean} 788 */ 789 _isDefined: function(object) 790 { 791 return !!object || this._isHTMLAllCollection(object); 792 }, 793 794 /** 795 * @param {*} object 796 * @return {boolean} 797 */ 798 _isHTMLAllCollection: function(object) 799 { 800 // document.all is reported as undefined, but we still want to process it. 801 return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object); 802 }, 803 804 /** 805 * @param {Object=} obj 806 * @return {string?} 807 */ 808 _subtype: function(obj) 809 { 810 if (obj === null) 811 return "null"; 812 813 if (this.isPrimitiveValue(obj)) 814 return null; 815 816 if (this._isHTMLAllCollection(obj)) 817 return "array"; 818 819 var preciseType = InjectedScriptHost.type(obj); 820 if (preciseType) 821 return preciseType; 822 823 // FireBug's array detection. 824 try { 825 if (typeof obj.splice === "function" && isFinite(obj.length)) 826 return "array"; 827 if (Object.prototype.toString.call(obj) === "[object Arguments]" && isFinite(obj.length)) // arguments. 828 return "array"; 829 } catch (e) { 830 } 831 832 // If owning frame has navigated to somewhere else window properties will be undefined. 833 return null; 834 }, 835 836 /** 837 * @param {*} obj 838 * @return {string?} 839 */ 840 _describe: function(obj) 841 { 842 if (this.isPrimitiveValue(obj)) 843 return null; 844 845 obj = /** @type {Object} */ (obj); 846 847 // Type is object, get subtype. 848 var subtype = this._subtype(obj); 849 850 if (subtype === "regexp") 851 return this._toString(obj); 852 853 if (subtype === "date") 854 return this._toString(obj); 855 856 if (subtype === "node") { 857 var description = obj.nodeName.toLowerCase(); 858 switch (obj.nodeType) { 859 case 1 /* Node.ELEMENT_NODE */: 860 description += obj.id ? "#" + obj.id : ""; 861 var className = obj.className; 862 description += className ? "." + className : ""; 863 break; 864 case 10 /*Node.DOCUMENT_TYPE_NODE */: 865 description = "<!DOCTYPE " + description + ">"; 866 break; 867 } 868 return description; 869 } 870 871 var className = InjectedScriptHost.internalConstructorName(obj); 872 if (subtype === "array") { 873 if (typeof obj.length === "number") 874 className += "[" + obj.length + "]"; 875 return className; 876 } 877 878 // NodeList in JSC is a function, check for array prior to this. 879 if (typeof obj === "function") 880 return this._toString(obj); 881 882 if (className === "Object") { 883 // In Chromium DOM wrapper prototypes will have Object as their constructor name, 884 // get the real DOM wrapper name from the constructor property. 885 var constructorName = obj.constructor && obj.constructor.name; 886 if (constructorName) 887 return constructorName; 888 } 889 return className; 890 }, 891 892 /** 893 * @param {*} obj 894 * @return {string} 895 */ 896 _toString: function(obj) 897 { 898 // We don't use String(obj) because inspectedWindow.String is undefined if owning frame navigated to another page. 899 return "" + obj; 900 } 901} 902 903/** 904 * @type {InjectedScript} 905 * @const 906 */ 907var injectedScript = new InjectedScript(); 908 909/** 910 * @constructor 911 * @param {*} object 912 * @param {string=} objectGroupName 913 * @param {boolean=} forceValueType 914 * @param {boolean=} generatePreview 915 * @param {?Array.<string>=} columnNames 916 */ 917InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames) 918{ 919 this.type = typeof object; 920 if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) { 921 // We don't send undefined values over JSON. 922 if (typeof object !== "undefined") 923 this.value = object; 924 925 // Null object is object with 'null' subtype' 926 if (object === null) 927 this.subtype = "null"; 928 929 // Provide user-friendly number values. 930 if (typeof object === "number") 931 this.description = object + ""; 932 return; 933 } 934 935 object = /** @type {Object} */ (object); 936 937 this.objectId = injectedScript._bind(object, objectGroupName); 938 var subtype = injectedScript._subtype(object); 939 if (subtype) 940 this.subtype = subtype; 941 this.className = InjectedScriptHost.internalConstructorName(object); 942 this.description = injectedScript._describe(object); 943 944 if (generatePreview && (this.type === "object" || injectedScript._isHTMLAllCollection(object))) 945 this.preview = this._generatePreview(object, undefined, columnNames); 946} 947 948InjectedScript.RemoteObject.prototype = { 949 /** 950 * @param {Object} object 951 * @param {Array.<string>=} firstLevelKeys 952 * @param {?Array.<string>=} secondLevelKeys 953 * @return {Object} preview 954 */ 955 _generatePreview: function(object, firstLevelKeys, secondLevelKeys) 956 { 957 var preview = {}; 958 preview.lossless = true; 959 preview.overflow = false; 960 preview.properties = []; 961 962 var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys; 963 var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0; 964 965 var propertiesThreshold = { 966 properties: isTableRowsRequest ? 1000 : Math.max(5, firstLevelKeysCount), 967 indexes: isTableRowsRequest ? 1000 : Math.max(100, firstLevelKeysCount) 968 }; 969 for (var o = object; injectedScript._isDefined(o); o = o.__proto__) 970 this._generateProtoPreview(o, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys); 971 return preview; 972 }, 973 974 /** 975 * @param {Object} object 976 * @param {Object} preview 977 * @param {Object} propertiesThreshold 978 * @param {Array.<string>=} firstLevelKeys 979 * @param {Array.<string>=} secondLevelKeys 980 */ 981 _generateProtoPreview: function(object, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys) 982 { 983 var propertyNames = firstLevelKeys ? firstLevelKeys : Object.keys(/** @type {!Object} */(object)); 984 try { 985 for (var i = 0; i < propertyNames.length; ++i) { 986 if (!propertiesThreshold.properties || !propertiesThreshold.indexes) { 987 preview.overflow = true; 988 preview.lossless = false; 989 break; 990 } 991 var name = propertyNames[i]; 992 if (this.subtype === "array" && name === "length") 993 continue; 994 995 var descriptor = Object.getOwnPropertyDescriptor(/** @type {!Object} */(object), name); 996 if (!("value" in descriptor) || !descriptor.enumerable) { 997 preview.lossless = false; 998 continue; 999 } 1000 1001 var value = descriptor.value; 1002 if (value === null) { 1003 this._appendPropertyPreview(preview, { name: name, type: "object", value: "null" }, propertiesThreshold); 1004 continue; 1005 } 1006 1007 const maxLength = 100; 1008 var type = typeof value; 1009 1010 if (InjectedScript.primitiveTypes[type]) { 1011 if (type === "string") { 1012 if (value.length > maxLength) { 1013 value = this._abbreviateString(value, maxLength, true); 1014 preview.lossless = false; 1015 } 1016 value = value.replace(/\n/g, "\u21B5"); 1017 } 1018 this._appendPropertyPreview(preview, { name: name, type: type, value: value + "" }, propertiesThreshold); 1019 continue; 1020 } 1021 1022 if (secondLevelKeys === null || secondLevelKeys) { 1023 var subPreview = this._generatePreview(value, secondLevelKeys || undefined); 1024 var property = { name: name, type: type, valuePreview: subPreview }; 1025 this._appendPropertyPreview(preview, property, propertiesThreshold); 1026 if (!subPreview.lossless) 1027 preview.lossless = false; 1028 if (subPreview.overflow) 1029 preview.overflow = true; 1030 continue; 1031 } 1032 1033 preview.lossless = false; 1034 1035 var subtype = injectedScript._subtype(value); 1036 var description = ""; 1037 if (type !== "function") 1038 description = this._abbreviateString(/** @type {string} */ (injectedScript._describe(value)), maxLength, subtype === "regexp"); 1039 1040 var property = { name: name, type: type, value: description }; 1041 if (subtype) 1042 property.subtype = subtype; 1043 this._appendPropertyPreview(preview, property, propertiesThreshold); 1044 } 1045 } catch (e) { 1046 } 1047 }, 1048 1049 /** 1050 * @param {Object} preview 1051 * @param {Object} property 1052 * @param {Object} propertiesThreshold 1053 */ 1054 _appendPropertyPreview: function(preview, property, propertiesThreshold) 1055 { 1056 if (isNaN(property.name)) 1057 propertiesThreshold.properties--; 1058 else 1059 propertiesThreshold.indexes--; 1060 preview.properties.push(property); 1061 }, 1062 1063 /** 1064 * @param {string} string 1065 * @param {number} maxLength 1066 * @param {boolean=} middle 1067 * @returns 1068 */ 1069 _abbreviateString: function(string, maxLength, middle) 1070 { 1071 if (string.length <= maxLength) 1072 return string; 1073 if (middle) { 1074 var leftHalf = maxLength >> 1; 1075 var rightHalf = maxLength - leftHalf - 1; 1076 return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf); 1077 } 1078 return string.substr(0, maxLength) + "\u2026"; 1079 } 1080} 1081/** 1082 * @constructor 1083 * @param {number} ordinal 1084 * @param {Object} callFrame 1085 */ 1086InjectedScript.CallFrameProxy = function(ordinal, callFrame) 1087{ 1088 this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}"; 1089 this.functionName = (callFrame.type === "function" ? callFrame.functionName : ""); 1090 this.location = { scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column }; 1091 this.scopeChain = this._wrapScopeChain(callFrame); 1092 this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace"); 1093} 1094 1095InjectedScript.CallFrameProxy.prototype = { 1096 /** 1097 * @param {Object} callFrame 1098 * @return {!Array.<DebuggerAgent.Scope>} 1099 */ 1100 _wrapScopeChain: function(callFrame) 1101 { 1102 var scopeChain = callFrame.scopeChain; 1103 var scopeChainProxy = []; 1104 for (var i = 0; i < scopeChain.length; i++) { 1105 var scope = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace"); 1106 scopeChainProxy.push(scope); 1107 } 1108 return scopeChainProxy; 1109 } 1110} 1111 1112/** 1113 * @param {number} scopeTypeCode 1114 * @param {*} scopeObject 1115 * @param {string} groupId 1116 * @return {!DebuggerAgent.Scope} 1117 */ 1118InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId) { 1119 const GLOBAL_SCOPE = 0; 1120 const LOCAL_SCOPE = 1; 1121 const WITH_SCOPE = 2; 1122 const CLOSURE_SCOPE = 3; 1123 const CATCH_SCOPE = 4; 1124 1125 /** @type {!Object.<number, string>} */ 1126 var scopeTypeNames = {}; 1127 scopeTypeNames[GLOBAL_SCOPE] = "global"; 1128 scopeTypeNames[LOCAL_SCOPE] = "local"; 1129 scopeTypeNames[WITH_SCOPE] = "with"; 1130 scopeTypeNames[CLOSURE_SCOPE] = "closure"; 1131 scopeTypeNames[CATCH_SCOPE] = "catch"; 1132 1133 return { 1134 object: injectedScript._wrapObject(scopeObject, groupId), 1135 type: /** @type {DebuggerAgent.ScopeType} */ (scopeTypeNames[scopeTypeCode]) 1136 }; 1137} 1138 1139/** 1140 * @constructor 1141 * @param {CommandLineAPIImpl} commandLineAPIImpl 1142 * @param {Object} callFrame 1143 */ 1144function CommandLineAPI(commandLineAPIImpl, callFrame) 1145{ 1146 /** 1147 * @param {string} member 1148 * @return {boolean} 1149 */ 1150 function inScopeVariables(member) 1151 { 1152 if (!callFrame) 1153 return false; 1154 1155 var scopeChain = callFrame.scopeChain; 1156 for (var i = 0; i < scopeChain.length; ++i) { 1157 if (member in scopeChain[i]) 1158 return true; 1159 } 1160 return false; 1161 } 1162 1163 /** 1164 * @param {string} name The name of the method for which a toString method should be generated. 1165 * @return {function():string} 1166 */ 1167 function customToStringMethod(name) 1168 { 1169 return function () { return "function " + name + "() { [Command Line API] }"; }; 1170 } 1171 1172 for (var i = 0; i < CommandLineAPI.members_.length; ++i) { 1173 var member = CommandLineAPI.members_[i]; 1174 if (member in inspectedWindow || inScopeVariables(member)) 1175 continue; 1176 1177 this[member] = bind(commandLineAPIImpl[member], commandLineAPIImpl); 1178 this[member].toString = customToStringMethod(member); 1179 } 1180 1181 for (var i = 0; i < 5; ++i) { 1182 var member = "$" + i; 1183 if (member in inspectedWindow || inScopeVariables(member)) 1184 continue; 1185 1186 this.__defineGetter__("$" + i, bind(commandLineAPIImpl._inspectedObject, commandLineAPIImpl, i)); 1187 } 1188 1189 this.$_ = injectedScript._lastResult; 1190} 1191 1192// NOTE: Please keep the list of API methods below snchronized to that in WebInspector.RuntimeModel! 1193/** 1194 * @type {Array.<string>} 1195 * @const 1196 */ 1197CommandLineAPI.members_ = [ 1198 "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd", 1199 "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners" 1200]; 1201 1202/** 1203 * @constructor 1204 */ 1205function CommandLineAPIImpl() 1206{ 1207} 1208 1209CommandLineAPIImpl.prototype = { 1210 /** 1211 * @param {string} selector 1212 * @param {Node=} start 1213 */ 1214 $: function (selector, start) 1215 { 1216 if (this._canQuerySelectorOnNode(start)) 1217 return start.querySelector(selector); 1218 1219 var result = inspectedWindow.document.querySelector(selector); 1220 if (result) 1221 return result; 1222 if (selector && selector[0] !== "#") { 1223 result = inspectedWindow.document.getElementById(selector); 1224 if (result) { 1225 inspectedWindow.console.warn("The console function $() has changed from $=getElementById(id) to $=querySelector(selector). You might try $(\"#%s\")", selector ); 1226 return null; 1227 } 1228 } 1229 return result; 1230 }, 1231 1232 /** 1233 * @param {string} selector 1234 * @param {Node=} start 1235 */ 1236 $$: function (selector, start) 1237 { 1238 if (this._canQuerySelectorOnNode(start)) 1239 return start.querySelectorAll(selector); 1240 return inspectedWindow.document.querySelectorAll(selector); 1241 }, 1242 1243 /** 1244 * @param {Node=} node 1245 * @return {boolean} 1246 */ 1247 _canQuerySelectorOnNode: function(node) 1248 { 1249 return !!node && InjectedScriptHost.type(node) === "node" && (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE); 1250 }, 1251 1252 /** 1253 * @param {string} xpath 1254 * @param {Node=} context 1255 */ 1256 $x: function(xpath, context) 1257 { 1258 var doc = (context && context.ownerDocument) || inspectedWindow.document; 1259 var result = doc.evaluate(xpath, context || doc, null, XPathResult.ANY_TYPE, null); 1260 switch (result.resultType) { 1261 case XPathResult.NUMBER_TYPE: 1262 return result.numberValue; 1263 case XPathResult.STRING_TYPE: 1264 return result.stringValue; 1265 case XPathResult.BOOLEAN_TYPE: 1266 return result.booleanValue; 1267 default: 1268 var nodes = []; 1269 var node; 1270 while (node = result.iterateNext()) 1271 nodes.push(node); 1272 return nodes; 1273 } 1274 }, 1275 1276 dir: function() 1277 { 1278 return inspectedWindow.console.dir.apply(inspectedWindow.console, arguments) 1279 }, 1280 1281 dirxml: function() 1282 { 1283 return inspectedWindow.console.dirxml.apply(inspectedWindow.console, arguments) 1284 }, 1285 1286 keys: function(object) 1287 { 1288 return Object.keys(object); 1289 }, 1290 1291 values: function(object) 1292 { 1293 var result = []; 1294 for (var key in object) 1295 result.push(object[key]); 1296 return result; 1297 }, 1298 1299 profile: function() 1300 { 1301 return inspectedWindow.console.profile.apply(inspectedWindow.console, arguments) 1302 }, 1303 1304 profileEnd: function() 1305 { 1306 return inspectedWindow.console.profileEnd.apply(inspectedWindow.console, arguments) 1307 }, 1308 1309 /** 1310 * @param {Object} object 1311 * @param {Array.<string>|string=} types 1312 */ 1313 monitorEvents: function(object, types) 1314 { 1315 if (!object || !object.addEventListener || !object.removeEventListener) 1316 return; 1317 types = this._normalizeEventTypes(types); 1318 for (var i = 0; i < types.length; ++i) { 1319 object.removeEventListener(types[i], this._logEvent, false); 1320 object.addEventListener(types[i], this._logEvent, false); 1321 } 1322 }, 1323 1324 /** 1325 * @param {Object} object 1326 * @param {Array.<string>|string=} types 1327 */ 1328 unmonitorEvents: function(object, types) 1329 { 1330 if (!object || !object.addEventListener || !object.removeEventListener) 1331 return; 1332 types = this._normalizeEventTypes(types); 1333 for (var i = 0; i < types.length; ++i) 1334 object.removeEventListener(types[i], this._logEvent, false); 1335 }, 1336 1337 /** 1338 * @param {*} object 1339 * @return {*} 1340 */ 1341 inspect: function(object) 1342 { 1343 return injectedScript._inspect(object); 1344 }, 1345 1346 copy: function(object) 1347 { 1348 if (injectedScript._subtype(object) === "node") 1349 object = object.outerHTML; 1350 InjectedScriptHost.copyText(object); 1351 }, 1352 1353 clear: function() 1354 { 1355 InjectedScriptHost.clearConsoleMessages(); 1356 }, 1357 1358 /** 1359 * @param {Node} node 1360 */ 1361 getEventListeners: function(node) 1362 { 1363 return InjectedScriptHost.getEventListeners(node); 1364 }, 1365 1366 /** 1367 * @param {number} num 1368 */ 1369 _inspectedObject: function(num) 1370 { 1371 return InjectedScriptHost.inspectedObject(num); 1372 }, 1373 1374 /** 1375 * @param {Array.<string>|string=} types 1376 * @return {Array.<string>} 1377 */ 1378 _normalizeEventTypes: function(types) 1379 { 1380 if (typeof types === "undefined") 1381 types = [ "mouse", "key", "touch", "control", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll", "search", "devicemotion", "deviceorientation" ]; 1382 else if (typeof types === "string") 1383 types = [ types ]; 1384 1385 var result = []; 1386 for (var i = 0; i < types.length; i++) { 1387 if (types[i] === "mouse") 1388 result.splice(0, 0, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout", "mousewheel"); 1389 else if (types[i] === "key") 1390 result.splice(0, 0, "keydown", "keyup", "keypress", "textInput"); 1391 else if (types[i] === "touch") 1392 result.splice(0, 0, "touchstart", "touchmove", "touchend", "touchcancel"); 1393 else if (types[i] === "control") 1394 result.splice(0, 0, "resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset"); 1395 else 1396 result.push(types[i]); 1397 } 1398 return result; 1399 }, 1400 1401 /** 1402 * @param {Event} event 1403 */ 1404 _logEvent: function(event) 1405 { 1406 inspectedWindow.console.log(event.type, event); 1407 } 1408} 1409 1410injectedScript._commandLineAPIImpl = new CommandLineAPIImpl(); 1411return injectedScript; 1412}) 1413