1 /* 2 * Copyright (C) 2007 Apple Inc. All rights reserved. 3 * Copyright (C) 2013 Google 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 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 /** 31 * @param {!InjectedScriptHostClass} InjectedScriptHost 32 * @param {!Window} inspectedWindow 33 * @param {number} injectedScriptId 34 */ 35 (function (InjectedScriptHost, inspectedWindow, injectedScriptId) { 36 37 /** 38 * Protect against Object overwritten by the user code. 39 * @suppress {duplicate} 40 */ 41 var Object = /** @type {function(new:Object, *=)} */ ({}.constructor); 42 43 /** 44 * @param {!Array.<T>} array 45 * @param {...} var_args 46 * @template T 47 */ 48 function push(array, var_args) 49 { 50 for (var i = 1; i < arguments.length; ++i) 51 array[array.length] = arguments[i]; 52 } 53 54 /** 55 * @param {!Arguments.<T>} array 56 * @param {number=} index 57 * @return {!Array.<T>} 58 * @template T 59 */ 60 function slice(array, index) 61 { 62 var result = []; 63 for (var i = index || 0, j = 0; i < array.length; ++i, ++j) 64 result[j] = array[i]; 65 return result; 66 } 67 68 /** 69 * @param {!Array.<T>} array1 70 * @param {!Array.<T>} array2 71 * @return {!Array.<T>} 72 * @template T 73 */ 74 function concat(array1, array2) 75 { 76 var result = []; 77 for (var i = 0; i < array1.length; ++i) 78 push(result, array1[i]); 79 for (var i = 0; i < array2.length; ++i) 80 push(result, array2[i]); 81 return result; 82 } 83 84 /** 85 * @param {*} obj 86 * @return {string} 87 */ 88 function toString(obj) 89 { 90 // We don't use String(obj) because it could be overriden. 91 return "" + obj; 92 } 93 94 /** 95 * @param {*} obj 96 * @return {string} 97 */ 98 function toStringDescription(obj) 99 { 100 if (typeof obj === "number" && obj === 0 && 1 / obj < 0) 101 return "-0"; // Negative zero. 102 return "" + obj; 103 } 104 105 /** 106 * Please use this bind, not the one from Function.prototype 107 * @param {function(...)} func 108 * @param {?Object} thisObject 109 * @param {...} var_args 110 * @return {function(...)} 111 */ 112 function bind(func, thisObject, var_args) 113 { 114 var args = slice(arguments, 2); 115 116 /** 117 * @param {...} var_args 118 */ 119 function bound(var_args) 120 { 121 return InjectedScriptHost.callFunction(func, thisObject, concat(args, slice(arguments))); 122 } 123 bound.toString = function() 124 { 125 return "bound: " + func; 126 }; 127 return bound; 128 } 129 130 /** 131 * @param {T} obj 132 * @return {T} 133 * @template T 134 */ 135 function nullifyObjectProto(obj) 136 { 137 if (obj && typeof obj === "object") 138 obj.__proto__ = null; 139 return obj; 140 } 141 142 /** 143 * @param {*} obj 144 * @return {boolean} 145 */ 146 function isUInt32(obj) 147 { 148 return typeof obj === "number" && obj >>> 0 === obj && (obj > 0 || 1 / obj > 0); 149 } 150 151 /** 152 * FireBug's array detection. 153 * @param {*} obj 154 * @return {boolean} 155 */ 156 function isArrayLike(obj) 157 { 158 if (typeof obj !== "object") 159 return false; 160 try { 161 if (typeof obj.splice === "function") 162 return isUInt32(obj.length); 163 } catch (e) { 164 } 165 return false; 166 } 167 168 /** 169 * @param {number} a 170 * @param {number} b 171 * @return {number} 172 */ 173 function max(a, b) 174 { 175 return a > b ? a : b; 176 } 177 178 /** 179 * FIXME: Remove once ES6 is supported natively by JS compiler. 180 * @param {*} obj 181 * @return {boolean} 182 */ 183 function isSymbol(obj) 184 { 185 var type = typeof obj; 186 return (type === "symbol"); 187 } 188 189 /** 190 * @constructor 191 */ 192 var InjectedScript = function() 193 { 194 /** @type {number} */ 195 this._lastBoundObjectId = 1; 196 /** @type {!Object.<number, (!Object|symbol)>} */ 197 this._idToWrappedObject = { __proto__: null }; 198 /** @type {!Object.<number, string>} */ 199 this._idToObjectGroupName = { __proto__: null }; 200 /** @type {!Object.<string, !Array.<number>>} */ 201 this._objectGroups = { __proto__: null }; 202 /** @type {!Object.<string, !Object>} */ 203 this._modules = { __proto__: null }; 204 } 205 206 /** 207 * @type {!Object.<string, boolean>} 208 * @const 209 */ 210 InjectedScript.primitiveTypes = { 211 "undefined": true, 212 "boolean": true, 213 "number": true, 214 "string": true, 215 __proto__: null 216 } 217 218 InjectedScript.prototype = { 219 /** 220 * @param {*} object 221 * @return {boolean} 222 */ 223 isPrimitiveValue: function(object) 224 { 225 // FIXME(33716): typeof document.all is always 'undefined'. 226 return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object); 227 }, 228 229 /** 230 * @param {*} object 231 * @param {string} groupName 232 * @param {boolean} canAccessInspectedWindow 233 * @param {boolean} generatePreview 234 * @return {!RuntimeAgent.RemoteObject} 235 */ 236 wrapObject: function(object, groupName, canAccessInspectedWindow, generatePreview) 237 { 238 if (canAccessInspectedWindow) 239 return this._wrapObject(object, groupName, false, generatePreview); 240 return this._fallbackWrapper(object); 241 }, 242 243 /** 244 * @param {*} object 245 * @return {!RuntimeAgent.RemoteObject} 246 */ 247 _fallbackWrapper: function(object) 248 { 249 var result = { __proto__: null }; 250 result.type = typeof object; 251 if (this.isPrimitiveValue(object)) 252 result.value = object; 253 else 254 result.description = toString(object); 255 return /** @type {!RuntimeAgent.RemoteObject} */ (result); 256 }, 257 258 /** 259 * @param {boolean} canAccessInspectedWindow 260 * @param {!Object} table 261 * @param {!Array.<string>|string|boolean} columns 262 * @return {!RuntimeAgent.RemoteObject} 263 */ 264 wrapTable: function(canAccessInspectedWindow, table, columns) 265 { 266 if (!canAccessInspectedWindow) 267 return this._fallbackWrapper(table); 268 var columnNames = null; 269 if (typeof columns === "string") 270 columns = [columns]; 271 if (InjectedScriptHost.subtype(columns) === "array") { 272 columnNames = []; 273 for (var i = 0; i < columns.length; ++i) 274 columnNames[i] = toString(columns[i]); 275 } 276 return this._wrapObject(table, "console", false, true, columnNames, true); 277 }, 278 279 /** 280 * @param {*} object 281 */ 282 inspectNode: function(object) 283 { 284 this._inspect(object); 285 }, 286 287 /** 288 * @param {*} object 289 * @return {*} 290 */ 291 _inspect: function(object) 292 { 293 if (arguments.length === 0) 294 return; 295 296 var objectId = this._wrapObject(object, ""); 297 var hints = { __proto__: null }; 298 299 InjectedScriptHost.inspect(objectId, hints); 300 return object; 301 }, 302 303 /** 304 * This method cannot throw. 305 * @param {*} object 306 * @param {string=} objectGroupName 307 * @param {boolean=} forceValueType 308 * @param {boolean=} generatePreview 309 * @param {?Array.<string>=} columnNames 310 * @param {boolean=} isTable 311 * @return {!RuntimeAgent.RemoteObject} 312 * @suppress {checkTypes} 313 */ 314 _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames, isTable) 315 { 316 try { 317 return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType, generatePreview, columnNames, isTable); 318 } catch (e) { 319 try { 320 var description = injectedScript._describe(e); 321 } catch (ex) { 322 var description = "<failed to convert exception to string>"; 323 } 324 return new InjectedScript.RemoteObject(description); 325 } 326 }, 327 328 /** 329 * @param {!Object|symbol} object 330 * @param {string=} objectGroupName 331 * @return {string} 332 */ 333 _bind: function(object, objectGroupName) 334 { 335 var id = this._lastBoundObjectId++; 336 this._idToWrappedObject[id] = object; 337 var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}"; 338 if (objectGroupName) { 339 var group = this._objectGroups[objectGroupName]; 340 if (!group) { 341 group = []; 342 this._objectGroups[objectGroupName] = group; 343 } 344 push(group, id); 345 this._idToObjectGroupName[id] = objectGroupName; 346 } 347 return objectId; 348 }, 349 350 /** 351 * @param {string} objectId 352 * @return {!Object} 353 */ 354 _parseObjectId: function(objectId) 355 { 356 return nullifyObjectProto(/** @type {!Object} */ (InjectedScriptHost.eval("(" + objectId + ")"))); 357 }, 358 359 /** 360 * @param {string} objectGroupName 361 */ 362 releaseObjectGroup: function(objectGroupName) 363 { 364 if (objectGroupName === "console") 365 delete this._lastResult; 366 var group = this._objectGroups[objectGroupName]; 367 if (!group) 368 return; 369 for (var i = 0; i < group.length; i++) 370 this._releaseObject(group[i]); 371 delete this._objectGroups[objectGroupName]; 372 }, 373 374 /** 375 * @param {string} methodName 376 * @param {string} args 377 * @return {*} 378 */ 379 dispatch: function(methodName, args) 380 { 381 var argsArray = /** @type {!Array.<*>} */ (InjectedScriptHost.eval("(" + args + ")")); 382 var result = InjectedScriptHost.callFunction(this[methodName], this, argsArray); 383 if (typeof result === "undefined") { 384 inspectedWindow.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName); 385 result = null; 386 } 387 return result; 388 }, 389 390 /** 391 * @param {string} objectId 392 * @param {boolean} ownProperties 393 * @param {boolean} accessorPropertiesOnly 394 * @return {!Array.<!RuntimeAgent.PropertyDescriptor>|boolean} 395 */ 396 getProperties: function(objectId, ownProperties, accessorPropertiesOnly) 397 { 398 var parsedObjectId = this._parseObjectId(objectId); 399 var object = this._objectForId(parsedObjectId); 400 var objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; 401 402 if (!this._isDefined(object) || isSymbol(object)) 403 return false; 404 object = /** @type {!Object} */ (object); 405 var descriptors = this._propertyDescriptors(object, ownProperties, accessorPropertiesOnly); 406 407 // Go over properties, wrap object values. 408 for (var i = 0; i < descriptors.length; ++i) { 409 var descriptor = descriptors[i]; 410 if ("get" in descriptor) 411 descriptor.get = this._wrapObject(descriptor.get, objectGroupName); 412 if ("set" in descriptor) 413 descriptor.set = this._wrapObject(descriptor.set, objectGroupName); 414 if ("value" in descriptor) 415 descriptor.value = this._wrapObject(descriptor.value, objectGroupName); 416 if (!("configurable" in descriptor)) 417 descriptor.configurable = false; 418 if (!("enumerable" in descriptor)) 419 descriptor.enumerable = false; 420 if ("symbol" in descriptor) 421 descriptor.symbol = this._wrapObject(descriptor.symbol, objectGroupName); 422 } 423 return descriptors; 424 }, 425 426 /** 427 * @param {string} objectId 428 * @return {!Array.<!Object>|boolean} 429 */ 430 getInternalProperties: function(objectId) 431 { 432 var parsedObjectId = this._parseObjectId(objectId); 433 var object = this._objectForId(parsedObjectId); 434 var objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; 435 if (!this._isDefined(object) || isSymbol(object)) 436 return false; 437 object = /** @type {!Object} */ (object); 438 var descriptors = []; 439 var internalProperties = InjectedScriptHost.getInternalProperties(object); 440 if (internalProperties) { 441 for (var i = 0; i < internalProperties.length; i++) { 442 var property = internalProperties[i]; 443 var descriptor = { 444 name: property.name, 445 value: this._wrapObject(property.value, objectGroupName), 446 __proto__: null 447 }; 448 push(descriptors, descriptor); 449 } 450 } 451 return descriptors; 452 }, 453 454 /** 455 * @param {string} functionId 456 * @return {!DebuggerAgent.FunctionDetails|string} 457 */ 458 getFunctionDetails: function(functionId) 459 { 460 var parsedFunctionId = this._parseObjectId(functionId); 461 var func = this._objectForId(parsedFunctionId); 462 if (typeof func !== "function") 463 return "Cannot resolve function by id."; 464 var details = nullifyObjectProto(/** @type {!DebuggerAgent.FunctionDetails} */ (InjectedScriptHost.functionDetails(func))); 465 if ("rawScopes" in details) { 466 var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id]; 467 var rawScopes = details["rawScopes"]; 468 delete details["rawScopes"]; 469 var scopes = []; 470 for (var i = 0; i < rawScopes.length; ++i) 471 scopes[i] = InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName); 472 details.scopeChain = scopes; 473 } 474 return details; 475 }, 476 477 /** 478 * @param {string} objectId 479 * @return {!Array.<!Object>|string} 480 */ 481 getCollectionEntries: function(objectId) 482 { 483 var parsedObjectId = this._parseObjectId(objectId); 484 var object = this._objectForId(parsedObjectId); 485 if (!object || typeof object !== "object") 486 return "Could not find object with given id"; 487 var entries = InjectedScriptHost.collectionEntries(object); 488 if (!entries) 489 return "Object with given id is not a collection"; 490 var objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; 491 for (var i = 0; i < entries.length; ++i) { 492 var entry = nullifyObjectProto(entries[i]); 493 if ("key" in entry) 494 entry.key = this._wrapObject(entry.key, objectGroupName); 495 entry.value = this._wrapObject(entry.value, objectGroupName); 496 entries[i] = entry; 497 } 498 return entries; 499 }, 500 501 /** 502 * @param {string} objectId 503 */ 504 releaseObject: function(objectId) 505 { 506 var parsedObjectId = this._parseObjectId(objectId); 507 this._releaseObject(parsedObjectId.id); 508 }, 509 510 /** 511 * @param {number} id 512 */ 513 _releaseObject: function(id) 514 { 515 delete this._idToWrappedObject[id]; 516 delete this._idToObjectGroupName[id]; 517 }, 518 519 /** 520 * @param {!Object} object 521 * @param {boolean=} ownProperties 522 * @param {boolean=} accessorPropertiesOnly 523 * @return {!Array.<!Object>} 524 */ 525 _propertyDescriptors: function(object, ownProperties, accessorPropertiesOnly) 526 { 527 var descriptors = []; 528 var propertyProcessed = { __proto__: null }; 529 530 /** 531 * @param {?Object} o 532 * @param {!Array.<string|symbol>} properties 533 */ 534 function process(o, properties) 535 { 536 for (var i = 0; i < properties.length; ++i) { 537 var property = properties[i]; 538 if (propertyProcessed[property]) 539 continue; 540 541 var name = property; 542 if (isSymbol(property)) 543 name = injectedScript._describe(property); 544 545 try { 546 propertyProcessed[property] = true; 547 var descriptor = nullifyObjectProto(InjectedScriptHost.suppressWarningsAndCallFunction(Object.getOwnPropertyDescriptor, Object, [o, property])); 548 if (descriptor) { 549 if (accessorPropertiesOnly && !("get" in descriptor || "set" in descriptor)) 550 continue; 551 } else { 552 // Not all bindings provide proper descriptors. Fall back to the writable, configurable property. 553 if (accessorPropertiesOnly) 554 continue; 555 try { 556 descriptor = { name: name, value: o[property], writable: false, configurable: false, enumerable: false, __proto__: null }; 557 if (o === object) 558 descriptor.isOwn = true; 559 push(descriptors, descriptor); 560 } catch (e) { 561 // Silent catch. 562 } 563 continue; 564 } 565 } catch (e) { 566 if (accessorPropertiesOnly) 567 continue; 568 var descriptor = { __proto__: null }; 569 descriptor.value = e; 570 descriptor.wasThrown = true; 571 } 572 573 descriptor.name = name; 574 if (o === object) 575 descriptor.isOwn = true; 576 if (isSymbol(property)) 577 descriptor.symbol = property; 578 push(descriptors, descriptor); 579 } 580 } 581 582 for (var o = object; this._isDefined(o); o = o.__proto__) { 583 // First call Object.keys() to enforce ordering of the property descriptors. 584 process(o, Object.keys(/** @type {!Object} */ (o))); 585 process(o, Object.getOwnPropertyNames(/** @type {!Object} */ (o))); 586 if (Object.getOwnPropertySymbols) 587 process(o, Object.getOwnPropertySymbols(/** @type {!Object} */ (o))); 588 589 if (ownProperties) { 590 if (object.__proto__ && !accessorPropertiesOnly) 591 push(descriptors, { name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true, __proto__: null }); 592 break; 593 } 594 } 595 596 return descriptors; 597 }, 598 599 /** 600 * @param {string} expression 601 * @param {string} objectGroup 602 * @param {boolean} injectCommandLineAPI 603 * @param {boolean} returnByValue 604 * @param {boolean} generatePreview 605 * @return {*} 606 */ 607 evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview) 608 { 609 return this._evaluateAndWrap(null, null, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview); 610 }, 611 612 /** 613 * @param {string} objectId 614 * @param {string} expression 615 * @param {string} args 616 * @param {boolean} returnByValue 617 * @return {!Object|string} 618 */ 619 callFunctionOn: function(objectId, expression, args, returnByValue) 620 { 621 var parsedObjectId = this._parseObjectId(objectId); 622 var object = this._objectForId(parsedObjectId); 623 if (!this._isDefined(object)) 624 return "Could not find object with given id"; 625 626 if (args) { 627 var resolvedArgs = []; 628 var callArgs = /** @type {!Array.<!RuntimeAgent.CallArgument>} */ (InjectedScriptHost.eval(args)); 629 for (var i = 0; i < callArgs.length; ++i) { 630 try { 631 resolvedArgs[i] = this._resolveCallArgument(callArgs[i]); 632 } catch (e) { 633 return toString(e); 634 } 635 } 636 } 637 638 try { 639 var objectGroup = this._idToObjectGroupName[parsedObjectId.id]; 640 var func = InjectedScriptHost.eval("(" + expression + ")"); 641 if (typeof func !== "function") 642 return "Given expression does not evaluate to a function"; 643 644 return { wasThrown: false, 645 result: this._wrapObject(InjectedScriptHost.callFunction(func, object, resolvedArgs), objectGroup, returnByValue), 646 __proto__: null }; 647 } catch (e) { 648 return this._createThrownValue(e, objectGroup, false); 649 } 650 }, 651 652 /** 653 * Resolves a value from CallArgument description. 654 * @param {!RuntimeAgent.CallArgument} callArgumentJson 655 * @return {*} resolved value 656 * @throws {string} error message 657 */ 658 _resolveCallArgument: function(callArgumentJson) 659 { 660 callArgumentJson = nullifyObjectProto(callArgumentJson); 661 var objectId = callArgumentJson.objectId; 662 if (objectId) { 663 var parsedArgId = this._parseObjectId(objectId); 664 if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId) 665 throw "Arguments should belong to the same JavaScript world as the target object."; 666 667 var resolvedArg = this._objectForId(parsedArgId); 668 if (!this._isDefined(resolvedArg)) 669 throw "Could not find object with given id"; 670 671 return resolvedArg; 672 } else if ("value" in callArgumentJson) { 673 var value = callArgumentJson.value; 674 if (callArgumentJson.type === "number" && typeof value !== "number") 675 value = Number(value); 676 return value; 677 } 678 return undefined; 679 }, 680 681 /** 682 * @param {?function(string):*} evalFunction 683 * @param {?Object} object 684 * @param {string} expression 685 * @param {string} objectGroup 686 * @param {boolean} isEvalOnCallFrame 687 * @param {boolean} injectCommandLineAPI 688 * @param {boolean} returnByValue 689 * @param {boolean} generatePreview 690 * @param {!Array.<!Object>=} scopeChain 691 * @return {!Object} 692 */ 693 _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview, scopeChain) 694 { 695 var wrappedResult = this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, scopeChain); 696 if (!wrappedResult.exceptionDetails) { 697 return { wasThrown: false, 698 result: this._wrapObject(wrappedResult.result, objectGroup, returnByValue, generatePreview), 699 __proto__: null }; 700 } 701 return this._createThrownValue(wrappedResult.result, objectGroup, generatePreview, wrappedResult.exceptionDetails); 702 }, 703 704 /** 705 * @param {*} value 706 * @param {string} objectGroup 707 * @param {boolean} generatePreview 708 * @param {!DebuggerAgent.ExceptionDetails=} exceptionDetails 709 * @return {!Object} 710 */ 711 _createThrownValue: function(value, objectGroup, generatePreview, exceptionDetails) 712 { 713 var remoteObject = this._wrapObject(value, objectGroup, false, generatePreview && !(value instanceof Error)); 714 if (!remoteObject.description){ 715 try { 716 remoteObject.description = toStringDescription(value); 717 } catch (e) {} 718 } 719 return { wasThrown: true, result: remoteObject, exceptionDetails: exceptionDetails, __proto__: null }; 720 }, 721 722 /** 723 * @param {?function(string):*} evalFunction 724 * @param {?Object} object 725 * @param {string} objectGroup 726 * @param {string} expression 727 * @param {boolean} isEvalOnCallFrame 728 * @param {boolean} injectCommandLineAPI 729 * @param {!Array.<!Object>=} scopeChain 730 * @return {*} 731 */ 732 _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, scopeChain) 733 { 734 // Only install command line api object for the time of evaluation. 735 // Surround the expression in with statements to inject our command line API so that 736 // the window object properties still take more precedent than our API functions. 737 738 injectCommandLineAPI = injectCommandLineAPI && !("__commandLineAPI" in inspectedWindow); 739 var injectScopeChain = scopeChain && scopeChain.length && !("__scopeChainForEval" in inspectedWindow); 740 741 try { 742 var prefix = ""; 743 var suffix = ""; 744 if (injectCommandLineAPI) { 745 InjectedScriptHost.setNonEnumProperty(inspectedWindow, "__commandLineAPI", new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null)); 746 prefix = "with (__commandLineAPI || { __proto__: null }) {"; 747 suffix = "}"; 748 } 749 if (injectScopeChain) { 750 InjectedScriptHost.setNonEnumProperty(inspectedWindow, "__scopeChainForEval", scopeChain); 751 for (var i = 0; i < scopeChain.length; ++i) { 752 prefix = "with (__scopeChainForEval[" + i + "] || { __proto__: null }) {" + (suffix ? " " : "") + prefix; 753 if (suffix) 754 suffix += " }"; 755 else 756 suffix = "}"; 757 } 758 } 759 760 if (prefix) 761 expression = prefix + "\n" + expression + "\n" + suffix; 762 var wrappedResult = evalFunction ? InjectedScriptHost.callFunction(evalFunction, object, [expression]) : InjectedScriptHost.evaluateWithExceptionDetails(expression); 763 if (objectGroup === "console" && !wrappedResult.exceptionDetails) 764 this._lastResult = wrappedResult.result; 765 return wrappedResult; 766 } finally { 767 if (injectCommandLineAPI) 768 delete inspectedWindow["__commandLineAPI"]; 769 if (injectScopeChain) 770 delete inspectedWindow["__scopeChainForEval"]; 771 } 772 }, 773 774 /** 775 * @param {?Object} callFrame 776 * @param {number} asyncOrdinal 777 * @return {!Array.<!InjectedScript.CallFrameProxy>|boolean} 778 */ 779 wrapCallFrames: function(callFrame, asyncOrdinal) 780 { 781 if (!callFrame) 782 return false; 783 784 var result = []; 785 var depth = 0; 786 do { 787 result[depth] = new InjectedScript.CallFrameProxy(depth, callFrame, asyncOrdinal); 788 callFrame = callFrame.caller; 789 ++depth; 790 } while (callFrame); 791 return result; 792 }, 793 794 /** 795 * @param {!Object} topCallFrame 796 * @param {!Array.<!Object>} asyncCallStacks 797 * @param {string} callFrameId 798 * @param {string} expression 799 * @param {string} objectGroup 800 * @param {boolean} injectCommandLineAPI 801 * @param {boolean} returnByValue 802 * @param {boolean} generatePreview 803 * @return {*} 804 */ 805 evaluateOnCallFrame: function(topCallFrame, asyncCallStacks, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview) 806 { 807 var parsedCallFrameId = nullifyObjectProto(/** @type {!Object} */ (InjectedScriptHost.eval("(" + callFrameId + ")"))); 808 var callFrame = this._callFrameForParsedId(topCallFrame, parsedCallFrameId, asyncCallStacks); 809 if (!callFrame) 810 return "Could not find call frame with given id"; 811 if (parsedCallFrameId["asyncOrdinal"]) 812 return this._evaluateAndWrap(null, null, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview, callFrame.scopeChain); 813 return this._evaluateAndWrap(callFrame.evaluateWithExceptionDetails, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview); 814 }, 815 816 /** 817 * @param {!Object} topCallFrame 818 * @param {string} callFrameId 819 * @return {*} 820 */ 821 restartFrame: function(topCallFrame, callFrameId) 822 { 823 var callFrame = this._callFrameForId(topCallFrame, callFrameId); 824 if (!callFrame) 825 return "Could not find call frame with given id"; 826 var result = callFrame.restart(); 827 if (result === false) 828 result = "Restart frame is not supported"; 829 return result; 830 }, 831 832 /** 833 * @param {!Object} topCallFrame 834 * @param {string} callFrameId 835 * @return {*} a stepIn position array ready for protocol JSON or a string error 836 */ 837 getStepInPositions: function(topCallFrame, callFrameId) 838 { 839 var callFrame = this._callFrameForId(topCallFrame, callFrameId); 840 if (!callFrame) 841 return "Could not find call frame with given id"; 842 var stepInPositionsUnpacked = JSON.parse(callFrame.stepInPositions); 843 if (typeof stepInPositionsUnpacked !== "object") 844 return "Step in positions not available"; 845 return stepInPositionsUnpacked; 846 }, 847 848 /** 849 * Either callFrameId or functionObjectId must be specified. 850 * @param {!Object} topCallFrame 851 * @param {string|boolean} callFrameId or false 852 * @param {string|boolean} functionObjectId or false 853 * @param {number} scopeNumber 854 * @param {string} variableName 855 * @param {string} newValueJsonString RuntimeAgent.CallArgument structure serialized as string 856 * @return {string|undefined} undefined if success or an error message 857 */ 858 setVariableValue: function(topCallFrame, callFrameId, functionObjectId, scopeNumber, variableName, newValueJsonString) 859 { 860 try { 861 var newValueJson = /** @type {!RuntimeAgent.CallArgument} */ (InjectedScriptHost.eval("(" + newValueJsonString + ")")); 862 var resolvedValue = this._resolveCallArgument(newValueJson); 863 if (typeof callFrameId === "string") { 864 var callFrame = this._callFrameForId(topCallFrame, callFrameId); 865 if (!callFrame) 866 return "Could not find call frame with given id"; 867 callFrame.setVariableValue(scopeNumber, variableName, resolvedValue) 868 } else { 869 var parsedFunctionId = this._parseObjectId(/** @type {string} */ (functionObjectId)); 870 var func = this._objectForId(parsedFunctionId); 871 if (typeof func !== "function") 872 return "Could not resolve function by id"; 873 InjectedScriptHost.setFunctionVariableValue(func, scopeNumber, variableName, resolvedValue); 874 } 875 } catch (e) { 876 return toString(e); 877 } 878 return undefined; 879 }, 880 881 /** 882 * @param {!Object} topCallFrame 883 * @param {string} callFrameId 884 * @return {?Object} 885 */ 886 _callFrameForId: function(topCallFrame, callFrameId) 887 { 888 var parsedCallFrameId = nullifyObjectProto(/** @type {!Object} */ (InjectedScriptHost.eval("(" + callFrameId + ")"))); 889 return this._callFrameForParsedId(topCallFrame, parsedCallFrameId, []); 890 }, 891 892 /** 893 * @param {!Object} topCallFrame 894 * @param {!Object} parsedCallFrameId 895 * @param {!Array.<!Object>} asyncCallStacks 896 * @return {?Object} 897 */ 898 _callFrameForParsedId: function(topCallFrame, parsedCallFrameId, asyncCallStacks) 899 { 900 var asyncOrdinal = parsedCallFrameId["asyncOrdinal"]; // 1-based index 901 if (asyncOrdinal) 902 topCallFrame = asyncCallStacks[asyncOrdinal - 1]; 903 var ordinal = parsedCallFrameId["ordinal"]; 904 var callFrame = topCallFrame; 905 while (--ordinal >= 0 && callFrame) 906 callFrame = callFrame.caller; 907 return callFrame; 908 }, 909 910 /** 911 * @param {!Object} objectId 912 * @return {!Object|symbol} 913 */ 914 _objectForId: function(objectId) 915 { 916 return this._idToWrappedObject[objectId.id]; 917 }, 918 919 /** 920 * @param {string} objectId 921 * @return {!Object|symbol} 922 */ 923 findObjectById: function(objectId) 924 { 925 var parsedObjectId = this._parseObjectId(objectId); 926 return this._objectForId(parsedObjectId); 927 }, 928 929 /** 930 * @param {string} objectId 931 * @return {?Node} 932 */ 933 nodeForObjectId: function(objectId) 934 { 935 var object = this.findObjectById(objectId); 936 if (!object || this._subtype(object) !== "node") 937 return null; 938 return /** @type {!Node} */ (object); 939 }, 940 941 /** 942 * @param {string} name 943 * @return {!Object} 944 */ 945 module: function(name) 946 { 947 return this._modules[name]; 948 }, 949 950 /** 951 * @param {string} name 952 * @param {string} source 953 * @return {?Object} 954 */ 955 injectModule: function(name, source) 956 { 957 delete this._modules[name]; 958 var moduleFunction = InjectedScriptHost.eval("(" + source + ")"); 959 if (typeof moduleFunction !== "function") { 960 inspectedWindow.console.error("Web Inspector error: A function was expected for module %s evaluation", name); 961 return null; 962 } 963 var module = /** @type {!Object} */ (InjectedScriptHost.callFunction(moduleFunction, inspectedWindow, [InjectedScriptHost, inspectedWindow, injectedScriptId, this])); 964 this._modules[name] = module; 965 return module; 966 }, 967 968 /** 969 * @param {*} object 970 * @return {boolean} 971 */ 972 _isDefined: function(object) 973 { 974 return !!object || this._isHTMLAllCollection(object); 975 }, 976 977 /** 978 * @param {*} object 979 * @return {boolean} 980 */ 981 _isHTMLAllCollection: function(object) 982 { 983 // document.all is reported as undefined, but we still want to process it. 984 return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object); 985 }, 986 987 /** 988 * @param {*} obj 989 * @return {?string} 990 */ 991 _subtype: function(obj) 992 { 993 if (obj === null) 994 return "null"; 995 996 if (this.isPrimitiveValue(obj)) 997 return null; 998 999 var subtype = InjectedScriptHost.subtype(obj); 1000 if (subtype) 1001 return subtype; 1002 1003 if (isArrayLike(obj)) 1004 return "array"; 1005 1006 // If owning frame has navigated to somewhere else window properties will be undefined. 1007 return null; 1008 }, 1009 1010 /** 1011 * @param {*} obj 1012 * @return {?string} 1013 */ 1014 _describe: function(obj) 1015 { 1016 if (this.isPrimitiveValue(obj)) 1017 return null; 1018 1019 var subtype = this._subtype(obj); 1020 1021 if (subtype === "regexp") 1022 return toString(obj); 1023 1024 if (subtype === "date") 1025 return toString(obj); 1026 1027 if (subtype === "node") { 1028 var description = obj.nodeName.toLowerCase(); 1029 switch (obj.nodeType) { 1030 case 1 /* Node.ELEMENT_NODE */: 1031 description += obj.id ? "#" + obj.id : ""; 1032 var className = obj.className; 1033 description += (className && typeof className === "string") ? "." + className.trim().replace(/\s+/g, ".") : ""; 1034 break; 1035 case 10 /*Node.DOCUMENT_TYPE_NODE */: 1036 description = "<!DOCTYPE " + description + ">"; 1037 break; 1038 } 1039 return description; 1040 } 1041 1042 var className = InjectedScriptHost.internalConstructorName(obj); 1043 if (subtype === "array") { 1044 if (typeof obj.length === "number") 1045 className += "[" + obj.length + "]"; 1046 return className; 1047 } 1048 1049 // NodeList in JSC is a function, check for array prior to this. 1050 if (typeof obj === "function") 1051 return toString(obj); 1052 1053 if (isSymbol(obj)) { 1054 try { 1055 return /** @type {string} */ (InjectedScriptHost.callFunction(Symbol.prototype.toString, obj)) || "Symbol"; 1056 } catch (e) { 1057 return "Symbol"; 1058 } 1059 } 1060 1061 if (obj instanceof Error && !!obj.message) 1062 return className + ": " + obj.message; 1063 1064 return className; 1065 } 1066 } 1067 1068 /** 1069 * @type {!InjectedScript} 1070 * @const 1071 */ 1072 var injectedScript = new InjectedScript(); 1073 1074 /** 1075 * @constructor 1076 * @param {*} object 1077 * @param {string=} objectGroupName 1078 * @param {boolean=} forceValueType 1079 * @param {boolean=} generatePreview 1080 * @param {?Array.<string>=} columnNames 1081 * @param {boolean=} isTable 1082 * @param {boolean=} skipEntriesPreview 1083 */ 1084 InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames, isTable, skipEntriesPreview) 1085 { 1086 this.type = typeof object; 1087 if (this.type === "undefined" && injectedScript._isHTMLAllCollection(object)) 1088 this.type = "object"; 1089 1090 if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) { 1091 // We don't send undefined values over JSON. 1092 if (this.type !== "undefined") 1093 this.value = object; 1094 1095 // Null object is object with 'null' subtype. 1096 if (object === null) 1097 this.subtype = "null"; 1098 1099 // Provide user-friendly number values. 1100 if (this.type === "number") { 1101 this.description = toStringDescription(object); 1102 // Override "value" property for values that can not be JSON-stringified. 1103 switch (this.description) { 1104 case "NaN": 1105 case "Infinity": 1106 case "-Infinity": 1107 case "-0": 1108 this.value = this.description; 1109 break; 1110 } 1111 } 1112 1113 return; 1114 } 1115 1116 object = /** @type {!Object} */ (object); 1117 1118 this.objectId = injectedScript._bind(object, objectGroupName); 1119 var subtype = injectedScript._subtype(object); 1120 if (subtype) 1121 this.subtype = subtype; 1122 var className = InjectedScriptHost.internalConstructorName(object); 1123 if (className) 1124 this.className = className; 1125 this.description = injectedScript._describe(object); 1126 1127 if (generatePreview && this.type === "object") 1128 this.preview = this._generatePreview(object, undefined, columnNames, isTable, skipEntriesPreview); 1129 } 1130 1131 InjectedScript.RemoteObject.prototype = { 1132 /** 1133 * @return {!RuntimeAgent.ObjectPreview} preview 1134 */ 1135 _createEmptyPreview: function() 1136 { 1137 var preview = { 1138 type: /** @type {!RuntimeAgent.ObjectPreviewType.<string>} */ (this.type), 1139 description: this.description || toStringDescription(this.value), 1140 lossless: true, 1141 overflow: false, 1142 properties: [], 1143 __proto__: null 1144 }; 1145 if (this.subtype) 1146 preview.subtype = /** @type {!RuntimeAgent.ObjectPreviewSubtype.<string>} */ (this.subtype); 1147 return preview; 1148 }, 1149 1150 /** 1151 * @param {!Object} object 1152 * @param {?Array.<string>=} firstLevelKeys 1153 * @param {?Array.<string>=} secondLevelKeys 1154 * @param {boolean=} isTable 1155 * @param {boolean=} skipEntriesPreview 1156 * @return {!RuntimeAgent.ObjectPreview} preview 1157 */ 1158 _generatePreview: function(object, firstLevelKeys, secondLevelKeys, isTable, skipEntriesPreview) 1159 { 1160 var preview = this._createEmptyPreview(); 1161 var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0; 1162 1163 var propertiesThreshold = { 1164 properties: isTable ? 1000 : max(5, firstLevelKeysCount), 1165 indexes: isTable ? 1000 : max(100, firstLevelKeysCount), 1166 __proto__: null 1167 }; 1168 1169 try { 1170 var descriptors = injectedScript._propertyDescriptors(object); 1171 1172 if (firstLevelKeys) { 1173 var nameToDescriptors = { __proto__: null }; 1174 for (var i = 0; i < descriptors.length; ++i) { 1175 var descriptor = descriptors[i]; 1176 nameToDescriptors["#" + descriptor.name] = descriptor; 1177 } 1178 descriptors = []; 1179 for (var i = 0; i < firstLevelKeys.length; ++i) 1180 descriptors[i] = nameToDescriptors["#" + firstLevelKeys[i]]; 1181 } 1182 1183 this._appendPropertyDescriptors(preview, descriptors, propertiesThreshold, secondLevelKeys, isTable); 1184 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) 1185 return preview; 1186 1187 // Add internal properties to preview. 1188 var internalProperties = InjectedScriptHost.getInternalProperties(object) || []; 1189 for (var i = 0; i < internalProperties.length; ++i) { 1190 internalProperties[i] = nullifyObjectProto(internalProperties[i]); 1191 internalProperties[i].enumerable = true; 1192 } 1193 this._appendPropertyDescriptors(preview, internalProperties, propertiesThreshold, secondLevelKeys, isTable); 1194 1195 if (this.subtype === "map" || this.subtype === "set") 1196 this._appendEntriesPreview(object, preview, skipEntriesPreview); 1197 1198 } catch (e) { 1199 preview.lossless = false; 1200 } 1201 1202 return preview; 1203 }, 1204 1205 /** 1206 * @param {!RuntimeAgent.ObjectPreview} preview 1207 * @param {!Array.<!Object>} descriptors 1208 * @param {!Object} propertiesThreshold 1209 * @param {?Array.<string>=} secondLevelKeys 1210 * @param {boolean=} isTable 1211 */ 1212 _appendPropertyDescriptors: function(preview, descriptors, propertiesThreshold, secondLevelKeys, isTable) 1213 { 1214 for (var i = 0; i < descriptors.length; ++i) { 1215 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) 1216 break; 1217 1218 var descriptor = descriptors[i]; 1219 if (!descriptor) 1220 continue; 1221 if (descriptor.wasThrown) { 1222 preview.lossless = false; 1223 continue; 1224 } 1225 if (!descriptor.enumerable && !descriptor.isOwn) 1226 continue; 1227 1228 var name = descriptor.name; 1229 if (name === "__proto__") 1230 continue; 1231 if (this.subtype === "array" && name === "length") 1232 continue; 1233 1234 if (!("value" in descriptor)) { 1235 preview.lossless = false; 1236 this._appendPropertyPreview(preview, { name: name, type: "accessor", __proto__: null }, propertiesThreshold); 1237 continue; 1238 } 1239 1240 var value = descriptor.value; 1241 if (value === null) { 1242 this._appendPropertyPreview(preview, { name: name, type: "object", subtype: "null", value: "null", __proto__: null }, propertiesThreshold); 1243 continue; 1244 } 1245 1246 var type = typeof value; 1247 if (!descriptor.enumerable && type === "function") 1248 continue; 1249 if (type === "undefined" && injectedScript._isHTMLAllCollection(value)) 1250 type = "object"; 1251 1252 var maxLength = 100; 1253 if (InjectedScript.primitiveTypes[type]) { 1254 if (type === "string" && value.length > maxLength) { 1255 value = this._abbreviateString(value, maxLength, true); 1256 preview.lossless = false; 1257 } 1258 this._appendPropertyPreview(preview, { name: name, type: type, value: toStringDescription(value), __proto__: null }, propertiesThreshold); 1259 continue; 1260 } 1261 1262 var property = { name: name, type: type, __proto__: null }; 1263 var subtype = injectedScript._subtype(value); 1264 if (subtype) 1265 property.subtype = subtype; 1266 1267 if (secondLevelKeys === null || secondLevelKeys) { 1268 var subPreview = this._generatePreview(value, secondLevelKeys || undefined, undefined, isTable); 1269 property.valuePreview = subPreview; 1270 if (!subPreview.lossless) 1271 preview.lossless = false; 1272 if (subPreview.overflow) 1273 preview.overflow = true; 1274 } else { 1275 var description = ""; 1276 if (type !== "function") 1277 description = this._abbreviateString(/** @type {string} */ (injectedScript._describe(value)), maxLength, subtype === "regexp"); 1278 property.value = description; 1279 preview.lossless = false; 1280 } 1281 this._appendPropertyPreview(preview, property, propertiesThreshold); 1282 } 1283 }, 1284 1285 /** 1286 * @param {!RuntimeAgent.ObjectPreview} preview 1287 * @param {!Object} property 1288 * @param {!Object} propertiesThreshold 1289 */ 1290 _appendPropertyPreview: function(preview, property, propertiesThreshold) 1291 { 1292 if (toString(property.name >>> 0) === property.name) 1293 propertiesThreshold.indexes--; 1294 else 1295 propertiesThreshold.properties--; 1296 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) { 1297 preview.overflow = true; 1298 preview.lossless = false; 1299 } else { 1300 push(preview.properties, property); 1301 } 1302 }, 1303 1304 /** 1305 * @param {!Object} object 1306 * @param {!RuntimeAgent.ObjectPreview} preview 1307 * @param {boolean=} skipEntriesPreview 1308 */ 1309 _appendEntriesPreview: function(object, preview, skipEntriesPreview) 1310 { 1311 var entries = InjectedScriptHost.collectionEntries(object); 1312 if (!entries) 1313 return; 1314 if (skipEntriesPreview) { 1315 if (entries.length) { 1316 preview.overflow = true; 1317 preview.lossless = false; 1318 } 1319 return; 1320 } 1321 preview.entries = []; 1322 var entriesThreshold = 5; 1323 for (var i = 0; i < entries.length; ++i) { 1324 if (preview.entries.length >= entriesThreshold) { 1325 preview.overflow = true; 1326 preview.lossless = false; 1327 break; 1328 } 1329 var entry = nullifyObjectProto(entries[i]); 1330 var previewEntry = { 1331 value: generateValuePreview(entry.value), 1332 __proto__: null 1333 }; 1334 if ("key" in entry) 1335 previewEntry.key = generateValuePreview(entry.key); 1336 push(preview.entries, previewEntry); 1337 } 1338 1339 /** 1340 * @param {*} value 1341 * @return {!RuntimeAgent.ObjectPreview} 1342 */ 1343 function generateValuePreview(value) 1344 { 1345 var remoteObject = new InjectedScript.RemoteObject(value, undefined, undefined, true, undefined, undefined, true); 1346 var valuePreview = remoteObject.preview || remoteObject._createEmptyPreview(); 1347 if (remoteObject.objectId) 1348 injectedScript.releaseObject(remoteObject.objectId); 1349 if (!valuePreview.lossless) 1350 preview.lossless = false; 1351 return valuePreview; 1352 } 1353 }, 1354 1355 /** 1356 * @param {string} string 1357 * @param {number} maxLength 1358 * @param {boolean=} middle 1359 * @return {string} 1360 */ 1361 _abbreviateString: function(string, maxLength, middle) 1362 { 1363 if (string.length <= maxLength) 1364 return string; 1365 if (middle) { 1366 var leftHalf = maxLength >> 1; 1367 var rightHalf = maxLength - leftHalf - 1; 1368 return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf); 1369 } 1370 return string.substr(0, maxLength) + "\u2026"; 1371 }, 1372 1373 __proto__: null 1374 } 1375 /** 1376 * @constructor 1377 * @param {number} ordinal 1378 * @param {!JavaScriptCallFrame} callFrame 1379 * @param {number} asyncOrdinal 1380 */ 1381 InjectedScript.CallFrameProxy = function(ordinal, callFrame, asyncOrdinal) 1382 { 1383 this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + (asyncOrdinal ? ",\"asyncOrdinal\":" + asyncOrdinal : "") + "}"; 1384 this.functionName = (callFrame.type === "function" ? callFrame.functionName : ""); 1385 this.location = { scriptId: toString(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column, __proto__: null }; 1386 this.scopeChain = this._wrapScopeChain(callFrame); 1387 this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace"); 1388 if (callFrame.isAtReturn) 1389 this.returnValue = injectedScript._wrapObject(callFrame.returnValue, "backtrace"); 1390 } 1391 1392 InjectedScript.CallFrameProxy.prototype = { 1393 /** 1394 * @param {!JavaScriptCallFrame} callFrame 1395 * @return {!Array.<!DebuggerAgent.Scope>} 1396 */ 1397 _wrapScopeChain: function(callFrame) 1398 { 1399 var scopeChain = callFrame.scopeChain; 1400 var scopeChainProxy = []; 1401 for (var i = 0; i < scopeChain.length; ++i) 1402 scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace"); 1403 return scopeChainProxy; 1404 }, 1405 1406 __proto__: null 1407 } 1408 1409 /** 1410 * @param {number} scopeTypeCode 1411 * @param {*} scopeObject 1412 * @param {string} groupId 1413 * @return {!DebuggerAgent.Scope} 1414 */ 1415 InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId) 1416 { 1417 const GLOBAL_SCOPE = 0; 1418 const LOCAL_SCOPE = 1; 1419 const WITH_SCOPE = 2; 1420 const CLOSURE_SCOPE = 3; 1421 const CATCH_SCOPE = 4; 1422 1423 /** @type {!Object.<number, string>} */ 1424 var scopeTypeNames = { __proto__: null }; 1425 scopeTypeNames[GLOBAL_SCOPE] = "global"; 1426 scopeTypeNames[LOCAL_SCOPE] = "local"; 1427 scopeTypeNames[WITH_SCOPE] = "with"; 1428 scopeTypeNames[CLOSURE_SCOPE] = "closure"; 1429 scopeTypeNames[CATCH_SCOPE] = "catch"; 1430 1431 return { 1432 object: injectedScript._wrapObject(scopeObject, groupId), 1433 type: /** @type {!DebuggerAgent.ScopeType} */ (scopeTypeNames[scopeTypeCode]), 1434 __proto__: null 1435 }; 1436 } 1437 1438 /** 1439 * @constructor 1440 * @param {!CommandLineAPIImpl} commandLineAPIImpl 1441 * @param {?Object} callFrame 1442 */ 1443 function CommandLineAPI(commandLineAPIImpl, callFrame) 1444 { 1445 /** 1446 * @param {string} member 1447 * @return {boolean} 1448 */ 1449 function inScopeVariables(member) 1450 { 1451 if (!callFrame) 1452 return false; 1453 1454 var scopeChain = callFrame.scopeChain; 1455 for (var i = 0; i < scopeChain.length; ++i) { 1456 if (member in scopeChain[i]) 1457 return true; 1458 } 1459 return false; 1460 } 1461 1462 /** 1463 * @param {string} name The name of the method for which a toString method should be generated. 1464 * @return {function():string} 1465 */ 1466 function customToStringMethod(name) 1467 { 1468 return function() 1469 { 1470 var funcArgsSyntax = ""; 1471 try { 1472 var funcSyntax = "" + commandLineAPIImpl[name]; 1473 funcSyntax = funcSyntax.replace(/\n/g, " "); 1474 funcSyntax = funcSyntax.replace(/^function[^\(]*\(([^\)]*)\).*$/, "$1"); 1475 funcSyntax = funcSyntax.replace(/\s*,\s*/g, ", "); 1476 funcSyntax = funcSyntax.replace(/\bopt_(\w+)\b/g, "[$1]"); 1477 funcArgsSyntax = funcSyntax.trim(); 1478 } catch (e) { 1479 } 1480 return "function " + name + "(" + funcArgsSyntax + ") { [Command Line API] }"; 1481 }; 1482 } 1483 1484 for (var i = 0; i < CommandLineAPI.members_.length; ++i) { 1485 var member = CommandLineAPI.members_[i]; 1486 if (member in inspectedWindow || inScopeVariables(member)) 1487 continue; 1488 1489 this[member] = bind(commandLineAPIImpl[member], commandLineAPIImpl); 1490 this[member].toString = customToStringMethod(member); 1491 } 1492 1493 for (var i = 0; i < 5; ++i) { 1494 var member = "$" + i; 1495 if (member in inspectedWindow || inScopeVariables(member)) 1496 continue; 1497 1498 this.__defineGetter__("$" + i, bind(commandLineAPIImpl._inspectedObject, commandLineAPIImpl, i)); 1499 } 1500 1501 this.$_ = injectedScript._lastResult; 1502 1503 this.__proto__ = null; 1504 } 1505 1506 // NOTE: Please keep the list of API methods below snchronized to that in WebInspector.RuntimeModel! 1507 // NOTE: Argument names of these methods will be printed in the console, so use pretty names! 1508 /** 1509 * @type {!Array.<string>} 1510 * @const 1511 */ 1512 CommandLineAPI.members_ = [ 1513 "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd", 1514 "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners", 1515 "debug", "undebug", "monitor", "unmonitor", "table" 1516 ]; 1517 1518 /** 1519 * @constructor 1520 */ 1521 function CommandLineAPIImpl() 1522 { 1523 } 1524 1525 CommandLineAPIImpl.prototype = { 1526 /** 1527 * @param {string} selector 1528 * @param {!Node=} opt_startNode 1529 * @return {*} 1530 */ 1531 $: function (selector, opt_startNode) 1532 { 1533 if (this._canQuerySelectorOnNode(opt_startNode)) 1534 return opt_startNode.querySelector(selector); 1535 1536 return inspectedWindow.document.querySelector(selector); 1537 }, 1538 1539 /** 1540 * @param {string} selector 1541 * @param {!Node=} opt_startNode 1542 * @return {*} 1543 */ 1544 $$: function (selector, opt_startNode) 1545 { 1546 if (this._canQuerySelectorOnNode(opt_startNode)) 1547 return opt_startNode.querySelectorAll(selector); 1548 return inspectedWindow.document.querySelectorAll(selector); 1549 }, 1550 1551 /** 1552 * @param {!Node=} node 1553 * @return {boolean} 1554 */ 1555 _canQuerySelectorOnNode: function(node) 1556 { 1557 return !!node && InjectedScriptHost.subtype(node) === "node" && (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE); 1558 }, 1559 1560 /** 1561 * @param {string} xpath 1562 * @param {!Node=} opt_startNode 1563 * @return {*} 1564 */ 1565 $x: function(xpath, opt_startNode) 1566 { 1567 var doc = (opt_startNode && opt_startNode.ownerDocument) || inspectedWindow.document; 1568 var result = doc.evaluate(xpath, opt_startNode || doc, null, XPathResult.ANY_TYPE, null); 1569 switch (result.resultType) { 1570 case XPathResult.NUMBER_TYPE: 1571 return result.numberValue; 1572 case XPathResult.STRING_TYPE: 1573 return result.stringValue; 1574 case XPathResult.BOOLEAN_TYPE: 1575 return result.booleanValue; 1576 default: 1577 var nodes = []; 1578 var node; 1579 while (node = result.iterateNext()) 1580 push(nodes, node); 1581 return nodes; 1582 } 1583 }, 1584 1585 /** 1586 * @return {*} 1587 */ 1588 dir: function(var_args) 1589 { 1590 return InjectedScriptHost.callFunction(inspectedWindow.console.dir, inspectedWindow.console, slice(arguments)); 1591 }, 1592 1593 /** 1594 * @return {*} 1595 */ 1596 dirxml: function(var_args) 1597 { 1598 return InjectedScriptHost.callFunction(inspectedWindow.console.dirxml, inspectedWindow.console, slice(arguments)); 1599 }, 1600 1601 /** 1602 * @return {!Array.<string>} 1603 */ 1604 keys: function(object) 1605 { 1606 return Object.keys(object); 1607 }, 1608 1609 /** 1610 * @return {!Array.<*>} 1611 */ 1612 values: function(object) 1613 { 1614 var result = []; 1615 for (var key in object) 1616 push(result, object[key]); 1617 return result; 1618 }, 1619 1620 /** 1621 * @return {*} 1622 */ 1623 profile: function(opt_title) 1624 { 1625 return InjectedScriptHost.callFunction(inspectedWindow.console.profile, inspectedWindow.console, slice(arguments)); 1626 }, 1627 1628 /** 1629 * @return {*} 1630 */ 1631 profileEnd: function(opt_title) 1632 { 1633 return InjectedScriptHost.callFunction(inspectedWindow.console.profileEnd, inspectedWindow.console, slice(arguments)); 1634 }, 1635 1636 /** 1637 * @param {!Object} object 1638 * @param {!Array.<string>|string=} opt_types 1639 */ 1640 monitorEvents: function(object, opt_types) 1641 { 1642 if (!object || !object.addEventListener || !object.removeEventListener) 1643 return; 1644 var types = this._normalizeEventTypes(opt_types); 1645 for (var i = 0; i < types.length; ++i) { 1646 object.removeEventListener(types[i], this._logEvent, false); 1647 object.addEventListener(types[i], this._logEvent, false); 1648 } 1649 }, 1650 1651 /** 1652 * @param {!Object} object 1653 * @param {!Array.<string>|string=} opt_types 1654 */ 1655 unmonitorEvents: function(object, opt_types) 1656 { 1657 if (!object || !object.addEventListener || !object.removeEventListener) 1658 return; 1659 var types = this._normalizeEventTypes(opt_types); 1660 for (var i = 0; i < types.length; ++i) 1661 object.removeEventListener(types[i], this._logEvent, false); 1662 }, 1663 1664 /** 1665 * @param {*} object 1666 * @return {*} 1667 */ 1668 inspect: function(object) 1669 { 1670 return injectedScript._inspect(object); 1671 }, 1672 1673 copy: function(object) 1674 { 1675 var string; 1676 if (injectedScript._subtype(object) === "node") { 1677 string = object.outerHTML; 1678 } else if (injectedScript.isPrimitiveValue(object)) { 1679 string = toString(object); 1680 } else { 1681 try { 1682 string = JSON.stringify(object, null, " "); 1683 } catch (e) { 1684 string = toString(object); 1685 } 1686 } 1687 1688 var hints = { copyToClipboard: true, __proto__: null }; 1689 var remoteObject = injectedScript._wrapObject(string, "") 1690 InjectedScriptHost.inspect(remoteObject, hints); 1691 }, 1692 1693 clear: function() 1694 { 1695 InjectedScriptHost.clearConsoleMessages(); 1696 }, 1697 1698 /** 1699 * @param {!Node} node 1700 * @return {!Array.<!{type: string, listener: function(), useCapture: boolean, remove: function()}>|undefined} 1701 */ 1702 getEventListeners: function(node) 1703 { 1704 var result = nullifyObjectProto(InjectedScriptHost.getEventListeners(node)); 1705 if (!result) 1706 return result; 1707 /** @this {{type: string, listener: function(), useCapture: boolean}} */ 1708 var removeFunc = function() 1709 { 1710 node.removeEventListener(this.type, this.listener, this.useCapture); 1711 } 1712 for (var type in result) { 1713 var listeners = result[type]; 1714 for (var i = 0, listener; listener = listeners[i]; ++i) { 1715 listener["type"] = type; 1716 listener["remove"] = removeFunc; 1717 } 1718 } 1719 return result; 1720 }, 1721 1722 debug: function(fn) 1723 { 1724 InjectedScriptHost.debugFunction(fn); 1725 }, 1726 1727 undebug: function(fn) 1728 { 1729 InjectedScriptHost.undebugFunction(fn); 1730 }, 1731 1732 monitor: function(fn) 1733 { 1734 InjectedScriptHost.monitorFunction(fn); 1735 }, 1736 1737 unmonitor: function(fn) 1738 { 1739 InjectedScriptHost.unmonitorFunction(fn); 1740 }, 1741 1742 table: function(data, opt_columns) 1743 { 1744 InjectedScriptHost.callFunction(inspectedWindow.console.table, inspectedWindow.console, slice(arguments)); 1745 }, 1746 1747 /** 1748 * @param {number} num 1749 */ 1750 _inspectedObject: function(num) 1751 { 1752 return InjectedScriptHost.inspectedObject(num); 1753 }, 1754 1755 /** 1756 * @param {!Array.<string>|string=} types 1757 * @return {!Array.<string>} 1758 */ 1759 _normalizeEventTypes: function(types) 1760 { 1761 if (typeof types === "undefined") 1762 types = ["mouse", "key", "touch", "control", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll", "search", "devicemotion", "deviceorientation"]; 1763 else if (typeof types === "string") 1764 types = [types]; 1765 1766 var result = []; 1767 for (var i = 0; i < types.length; ++i) { 1768 if (types[i] === "mouse") 1769 push(result, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout", "mousewheel"); 1770 else if (types[i] === "key") 1771 push(result, "keydown", "keyup", "keypress", "textInput"); 1772 else if (types[i] === "touch") 1773 push(result, "touchstart", "touchmove", "touchend", "touchcancel"); 1774 else if (types[i] === "control") 1775 push(result, "resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset"); 1776 else 1777 push(result, types[i]); 1778 } 1779 return result; 1780 }, 1781 1782 /** 1783 * @param {!Event} event 1784 */ 1785 _logEvent: function(event) 1786 { 1787 inspectedWindow.console.log(event.type, event); 1788 } 1789 } 1790 1791 injectedScript._commandLineAPIImpl = new CommandLineAPIImpl(); 1792 return injectedScript; 1793 }) 1794