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