2 * Copyright (C) 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2013 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
31 * @param {InjectedScriptHost} InjectedScriptHost
32 * @param {Window} inspectedWindow
33 * @param {number} injectedScriptId
35 (function (InjectedScriptHost, inspectedWindow, injectedScriptId) {
38 * Protect against Object overwritten by the user code.
39 * @suppress {duplicate}
41 var Object = /** @type {function(new:Object, *=)} */ ({}.constructor);
44 * @param {!Array.<T>} array
45 * @param {...} var_args
48 function push(array, var_args)
50 for (var i = 1; i < arguments.length; ++i)
51 array[array.length] = arguments[i];
55 * @param {!Arguments.<T>} array
56 * @param {number=} index
57 * @return {!Array.<T>}
60 function slice(array, index)
63 for (var i = index || 0, j = 0; i < array.length; ++i, ++j)
69 * @param {!Array.<T>} array1
70 * @param {!Array.<T>} array2
71 * @return {!Array.<T>}
74 function concat(array1, array2)
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]);
88 function toString(obj)
90 // We don't use String(obj) because it could be overriden.
98 function toStringDescription(obj)
100 if (typeof obj === "number" && obj === 0 && 1 / obj < 0)
101 return "-0"; // Negative zero.
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(...)}
112 function bind(func, thisObject, var_args)
114 var args = slice(arguments, 2);
117 * @param {...} var_args
119 function bound(var_args)
121 return InjectedScriptHost.callFunction(func, thisObject, concat(args, slice(arguments)));
123 bound.toString = function()
125 return "bound: " + func;
135 function nullifyObjectProto(obj)
137 if (obj && typeof obj === "object")
138 obj.__proto__ = null;
143 * FireBug's array detection.
147 function isArrayLike(obj)
150 if (typeof obj !== "object")
152 if (typeof obj.splice === "function")
153 return isFinite(obj.length);
154 var str = InjectedScriptHost.callFunction(Object.prototype.toString, obj);
155 if (str === "[object Arguments]")
156 return isFinite(obj.length);
169 return a > b ? a : b;
173 * FIXME: Remove once ES6 is supported natively by JS compiler.
177 function isSymbol(obj)
179 var type = typeof obj;
180 return (type === "symbol");
186 var InjectedScript = function()
188 /** @type {number} */
189 this._lastBoundObjectId = 1;
190 /** @type {!Object.<number, (!Object|symbol)>} */
191 this._idToWrappedObject = { __proto__: null };
192 /** @type {!Object.<number, string>} */
193 this._idToObjectGroupName = { __proto__: null };
194 /** @type {!Object.<string, !Array.<number>>} */
195 this._objectGroups = { __proto__: null };
196 /** @type {!Object.<string, !Object>} */
197 this._modules = { __proto__: null };
201 * @type {!Object.<string, boolean>}
204 InjectedScript.primitiveTypes = {
212 InjectedScript.prototype = {
217 isPrimitiveValue: function(object)
219 // FIXME(33716): typeof document.all is always 'undefined'.
220 return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
225 * @param {string} groupName
226 * @param {boolean} canAccessInspectedWindow
227 * @param {boolean} generatePreview
228 * @return {!RuntimeAgent.RemoteObject}
230 wrapObject: function(object, groupName, canAccessInspectedWindow, generatePreview)
232 if (canAccessInspectedWindow)
233 return this._wrapObject(object, groupName, false, generatePreview);
234 return this._fallbackWrapper(object);
239 * @return {!RuntimeAgent.RemoteObject}
241 _fallbackWrapper: function(object)
243 var result = { __proto__: null };
244 result.type = typeof object;
245 if (this.isPrimitiveValue(object))
246 result.value = object;
248 result.description = toString(object);
249 return /** @type {!RuntimeAgent.RemoteObject} */ (result);
253 * @param {boolean} canAccessInspectedWindow
254 * @param {!Object} table
255 * @param {!Array.<string>|string|boolean} columns
256 * @return {!RuntimeAgent.RemoteObject}
258 wrapTable: function(canAccessInspectedWindow, table, columns)
260 if (!canAccessInspectedWindow)
261 return this._fallbackWrapper(table);
262 var columnNames = null;
263 if (typeof columns === "string")
265 if (InjectedScriptHost.type(columns) == "array") {
267 for (var i = 0; i < columns.length; ++i)
268 columnNames[i] = toString(columns[i]);
270 return this._wrapObject(table, "console", false, true, columnNames, true);
276 inspectNode: function(object)
278 this._inspect(object);
285 _inspect: function(object)
287 if (arguments.length === 0)
290 var objectId = this._wrapObject(object, "");
291 var hints = { __proto__: null };
293 InjectedScriptHost.inspect(objectId, hints);
298 * This method cannot throw.
300 * @param {string=} objectGroupName
301 * @param {boolean=} forceValueType
302 * @param {boolean=} generatePreview
303 * @param {?Array.<string>=} columnNames
304 * @param {boolean=} isTable
305 * @return {!RuntimeAgent.RemoteObject}
306 * @suppress {checkTypes}
308 _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames, isTable)
311 return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType, generatePreview, columnNames, isTable);
314 var description = injectedScript._describe(e);
316 var description = "<failed to convert exception to string>";
318 return new InjectedScript.RemoteObject(description);
323 * @param {!Object|symbol} object
324 * @param {string=} objectGroupName
327 _bind: function(object, objectGroupName)
329 var id = this._lastBoundObjectId++;
330 this._idToWrappedObject[id] = object;
331 var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}";
332 if (objectGroupName) {
333 var group = this._objectGroups[objectGroupName];
336 this._objectGroups[objectGroupName] = group;
339 this._idToObjectGroupName[id] = objectGroupName;
345 * @param {string} objectId
348 _parseObjectId: function(objectId)
350 return nullifyObjectProto(InjectedScriptHost.eval("(" + objectId + ")"));
354 * @param {string} objectGroupName
356 releaseObjectGroup: function(objectGroupName)
358 if (objectGroupName === "console")
359 delete this._lastResult;
360 var group = this._objectGroups[objectGroupName];
363 for (var i = 0; i < group.length; i++)
364 this._releaseObject(group[i]);
365 delete this._objectGroups[objectGroupName];
369 * @param {string} methodName
370 * @param {string} args
373 dispatch: function(methodName, args)
375 var argsArray = InjectedScriptHost.eval("(" + args + ")");
376 var result = InjectedScriptHost.callFunction(this[methodName], this, argsArray);
377 if (typeof result === "undefined") {
378 inspectedWindow.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
385 * @param {string} objectId
386 * @param {boolean} ownProperties
387 * @param {boolean} accessorPropertiesOnly
388 * @return {!Array.<!RuntimeAgent.PropertyDescriptor>|boolean}
390 getProperties: function(objectId, ownProperties, accessorPropertiesOnly)
392 var parsedObjectId = this._parseObjectId(objectId);
393 var object = this._objectForId(parsedObjectId);
394 var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
396 if (!this._isDefined(object) || isSymbol(object))
398 object = /** @type {!Object} */ (object);
399 var descriptors = this._propertyDescriptors(object, ownProperties, accessorPropertiesOnly);
401 // Go over properties, wrap object values.
402 for (var i = 0; i < descriptors.length; ++i) {
403 var descriptor = descriptors[i];
404 if ("get" in descriptor)
405 descriptor.get = this._wrapObject(descriptor.get, objectGroupName);
406 if ("set" in descriptor)
407 descriptor.set = this._wrapObject(descriptor.set, objectGroupName);
408 if ("value" in descriptor)
409 descriptor.value = this._wrapObject(descriptor.value, objectGroupName);
410 if (!("configurable" in descriptor))
411 descriptor.configurable = false;
412 if (!("enumerable" in descriptor))
413 descriptor.enumerable = false;
414 if ("symbol" in descriptor)
415 descriptor.symbol = this._wrapObject(descriptor.symbol, objectGroupName);
421 * @param {string} objectId
422 * @return {!Array.<!Object>|boolean}
424 getInternalProperties: function(objectId)
426 var parsedObjectId = this._parseObjectId(objectId);
427 var object = this._objectForId(parsedObjectId);
428 var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
429 if (!this._isDefined(object) || isSymbol(object))
431 object = /** @type {!Object} */ (object);
432 var descriptors = [];
433 var internalProperties = InjectedScriptHost.getInternalProperties(object);
434 if (internalProperties) {
435 for (var i = 0; i < internalProperties.length; i++) {
436 var property = internalProperties[i];
439 value: this._wrapObject(property.value, objectGroupName),
442 push(descriptors, descriptor);
449 * @param {string} functionId
450 * @return {!DebuggerAgent.FunctionDetails|string}
452 getFunctionDetails: function(functionId)
454 var parsedFunctionId = this._parseObjectId(functionId);
455 var func = this._objectForId(parsedFunctionId);
456 if (typeof func !== "function")
457 return "Cannot resolve function by id.";
458 var details = nullifyObjectProto(InjectedScriptHost.functionDetails(func));
459 if ("rawScopes" in details) {
460 var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id];
461 var rawScopes = details.rawScopes;
462 delete details.rawScopes;
464 for (var i = 0; i < rawScopes.length; ++i)
465 scopes[i] = InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName);
466 details.scopeChain = scopes;
472 * @param {string} objectId
474 releaseObject: function(objectId)
476 var parsedObjectId = this._parseObjectId(objectId);
477 this._releaseObject(parsedObjectId.id);
483 _releaseObject: function(id)
485 delete this._idToWrappedObject[id];
486 delete this._idToObjectGroupName[id];
490 * @param {!Object} object
491 * @param {boolean=} ownProperties
492 * @param {boolean=} accessorPropertiesOnly
493 * @return {!Array.<!Object>}
495 _propertyDescriptors: function(object, ownProperties, accessorPropertiesOnly)
497 var descriptors = [];
498 var propertyProcessed = { __proto__: null };
502 * @param {!Array.<string|symbol>} properties
504 function process(o, properties)
506 for (var i = 0; i < properties.length; ++i) {
507 var property = properties[i];
508 if (propertyProcessed[property])
512 if (isSymbol(property))
513 name = injectedScript._describe(property);
516 propertyProcessed[property] = true;
517 var descriptor = nullifyObjectProto(InjectedScriptHost.suppressWarningsAndCallFunction(Object.getOwnPropertyDescriptor, Object, [o, property]));
519 if (accessorPropertiesOnly && !("get" in descriptor || "set" in descriptor))
522 // Not all bindings provide proper descriptors. Fall back to the writable, configurable property.
523 if (accessorPropertiesOnly)
526 descriptor = { name: name, value: o[property], writable: false, configurable: false, enumerable: false, __proto__: null };
528 descriptor.isOwn = true;
529 push(descriptors, descriptor);
536 if (accessorPropertiesOnly)
538 var descriptor = { __proto__: null };
539 descriptor.value = e;
540 descriptor.wasThrown = true;
543 descriptor.name = name;
545 descriptor.isOwn = true;
546 if (isSymbol(property))
547 descriptor.symbol = property;
548 push(descriptors, descriptor);
552 for (var o = object; this._isDefined(o); o = o.__proto__) {
553 // First call Object.keys() to enforce ordering of the property descriptors.
554 process(o, Object.keys(/** @type {!Object} */ (o)));
555 process(o, Object.getOwnPropertyNames(/** @type {!Object} */ (o)));
556 if (Object.getOwnPropertySymbols)
557 process(o, Object.getOwnPropertySymbols(/** @type {!Object} */ (o)));
560 if (object.__proto__ && !accessorPropertiesOnly)
561 push(descriptors, { name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true, __proto__: null });
570 * @param {string} expression
571 * @param {string} objectGroup
572 * @param {boolean} injectCommandLineAPI
573 * @param {boolean} returnByValue
574 * @param {boolean} generatePreview
577 evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview)
579 return this._evaluateAndWrap(null, null, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview);
583 * @param {string} objectId
584 * @param {string} expression
585 * @param {string} args
586 * @param {boolean} returnByValue
587 * @return {!Object|string}
589 callFunctionOn: function(objectId, expression, args, returnByValue)
591 var parsedObjectId = this._parseObjectId(objectId);
592 var object = this._objectForId(parsedObjectId);
593 if (!this._isDefined(object))
594 return "Could not find object with given id";
597 var resolvedArgs = [];
598 args = InjectedScriptHost.eval(args);
599 for (var i = 0; i < args.length; ++i) {
601 resolvedArgs[i] = this._resolveCallArgument(args[i]);
609 var objectGroup = this._idToObjectGroupName[parsedObjectId.id];
610 var func = InjectedScriptHost.eval("(" + expression + ")");
611 if (typeof func !== "function")
612 return "Given expression does not evaluate to a function";
614 return { wasThrown: false,
615 result: this._wrapObject(InjectedScriptHost.callFunction(func, object, resolvedArgs), objectGroup, returnByValue),
618 return this._createThrownValue(e, objectGroup, false);
623 * Resolves a value from CallArgument description.
624 * @param {!RuntimeAgent.CallArgument} callArgumentJson
625 * @return {*} resolved value
626 * @throws {string} error message
628 _resolveCallArgument: function(callArgumentJson)
630 callArgumentJson = nullifyObjectProto(callArgumentJson);
631 var objectId = callArgumentJson.objectId;
633 var parsedArgId = this._parseObjectId(objectId);
634 if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId)
635 throw "Arguments should belong to the same JavaScript world as the target object.";
637 var resolvedArg = this._objectForId(parsedArgId);
638 if (!this._isDefined(resolvedArg))
639 throw "Could not find object with given id";
642 } else if ("value" in callArgumentJson) {
643 var value = callArgumentJson.value;
644 if (callArgumentJson.type === "number" && typeof value !== "number")
645 value = Number(value);
652 * @param {?function(string):*} evalFunction
653 * @param {?Object} object
654 * @param {string} expression
655 * @param {string} objectGroup
656 * @param {boolean} isEvalOnCallFrame
657 * @param {boolean} injectCommandLineAPI
658 * @param {boolean} returnByValue
659 * @param {boolean} generatePreview
660 * @param {!Array.<!Object>=} scopeChain
663 _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview, scopeChain)
665 var wrappedResult = this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, scopeChain);
666 if (!wrappedResult.exceptionDetails) {
667 return { wasThrown: false,
668 result: this._wrapObject(wrappedResult.result, objectGroup, returnByValue, generatePreview),
671 return this._createThrownValue(wrappedResult.result, objectGroup, generatePreview, wrappedResult.exceptionDetails);
676 * @param {string} objectGroup
677 * @param {boolean} generatePreview
678 * @param {!DebuggerAgent.ExceptionDetails=} exceptionDetails
681 _createThrownValue: function(value, objectGroup, generatePreview, exceptionDetails)
683 var remoteObject = this._wrapObject(value, objectGroup, false, generatePreview && !(value instanceof Error));
684 if (!remoteObject.description){
686 remoteObject.description = toStringDescription(value);
689 return { wasThrown: true, result: remoteObject, exceptionDetails: exceptionDetails, __proto__: null };
693 * @param {?function(string):*} evalFunction
694 * @param {?Object} object
695 * @param {string} objectGroup
696 * @param {string} expression
697 * @param {boolean} isEvalOnCallFrame
698 * @param {boolean} injectCommandLineAPI
699 * @param {!Array.<!Object>=} scopeChain
702 _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, scopeChain)
704 // Only install command line api object for the time of evaluation.
705 // Surround the expression in with statements to inject our command line API so that
706 // the window object properties still take more precedent than our API functions.
708 injectCommandLineAPI = injectCommandLineAPI && !("__commandLineAPI" in inspectedWindow);
709 var injectScopeChain = scopeChain && scopeChain.length && !("__scopeChainForEval" in inspectedWindow);
714 if (injectCommandLineAPI) {
715 inspectedWindow.__commandLineAPI = new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
716 prefix = "with (__commandLineAPI || { __proto__: null }) {";
719 if (injectScopeChain) {
720 inspectedWindow.__scopeChainForEval = scopeChain;
721 for (var i = 0; i < scopeChain.length; ++i) {
722 prefix = "with (__scopeChainForEval[" + i + "] || { __proto__: null }) {" + (suffix ? " " : "") + prefix;
731 expression = prefix + "\n" + expression + "\n" + suffix;
732 var wrappedResult = evalFunction ? InjectedScriptHost.callFunction(evalFunction, object, [expression]) : InjectedScriptHost.evaluateWithExceptionDetails(expression);
733 if (objectGroup === "console" && !wrappedResult.exceptionDetails)
734 this._lastResult = wrappedResult.result;
735 return wrappedResult;
737 if (injectCommandLineAPI)
738 delete inspectedWindow.__commandLineAPI;
739 if (injectScopeChain)
740 delete inspectedWindow.__scopeChainForEval;
745 * @param {?Object} callFrame
746 * @param {number} asyncOrdinal
747 * @return {!Array.<!InjectedScript.CallFrameProxy>|boolean}
749 wrapCallFrames: function(callFrame, asyncOrdinal)
757 result[depth] = new InjectedScript.CallFrameProxy(depth, callFrame, asyncOrdinal);
758 callFrame = callFrame.caller;
765 * @param {!Object} topCallFrame
766 * @param {!Array.<!Object>} asyncCallStacks
767 * @param {string} callFrameId
768 * @param {string} expression
769 * @param {string} objectGroup
770 * @param {boolean} injectCommandLineAPI
771 * @param {boolean} returnByValue
772 * @param {boolean} generatePreview
775 evaluateOnCallFrame: function(topCallFrame, asyncCallStacks, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview)
777 var parsedCallFrameId = nullifyObjectProto(InjectedScriptHost.eval("(" + callFrameId + ")"));
778 var callFrame = this._callFrameForParsedId(topCallFrame, parsedCallFrameId, asyncCallStacks);
780 return "Could not find call frame with given id";
781 if (parsedCallFrameId["asyncOrdinal"])
782 return this._evaluateAndWrap(null, null, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview, callFrame.scopeChain);
783 return this._evaluateAndWrap(callFrame.evaluateWithExceptionDetails, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview);
787 * @param {!Object} topCallFrame
788 * @param {string} callFrameId
791 restartFrame: function(topCallFrame, callFrameId)
793 var callFrame = this._callFrameForId(topCallFrame, callFrameId);
795 return "Could not find call frame with given id";
796 var result = callFrame.restart();
797 if (result === false)
798 result = "Restart frame is not supported";
803 * @param {!Object} topCallFrame
804 * @param {string} callFrameId
805 * @return {*} a stepIn position array ready for protocol JSON or a string error
807 getStepInPositions: function(topCallFrame, callFrameId)
809 var callFrame = this._callFrameForId(topCallFrame, callFrameId);
811 return "Could not find call frame with given id";
812 var stepInPositionsUnpacked = JSON.parse(callFrame.stepInPositions);
813 if (typeof stepInPositionsUnpacked !== "object")
814 return "Step in positions not available";
815 return stepInPositionsUnpacked;
819 * Either callFrameId or functionObjectId must be specified.
820 * @param {!Object} topCallFrame
821 * @param {string|boolean} callFrameId or false
822 * @param {string|boolean} functionObjectId or false
823 * @param {number} scopeNumber
824 * @param {string} variableName
825 * @param {string} newValueJsonString RuntimeAgent.CallArgument structure serialized as string
826 * @return {string|undefined} undefined if success or an error message
828 setVariableValue: function(topCallFrame, callFrameId, functionObjectId, scopeNumber, variableName, newValueJsonString)
831 if (typeof callFrameId === "string") {
832 var callFrame = this._callFrameForId(topCallFrame, callFrameId);
834 return "Could not find call frame with given id";
835 setter = bind(callFrame.setVariableValue, callFrame);
837 var parsedFunctionId = this._parseObjectId(/** @type {string} */ (functionObjectId));
838 var func = this._objectForId(parsedFunctionId);
839 if (typeof func !== "function")
840 return "Cannot resolve function by id.";
841 setter = bind(InjectedScriptHost.setFunctionVariableValue, InjectedScriptHost, func);
845 newValueJson = InjectedScriptHost.eval("(" + newValueJsonString + ")");
847 return "Failed to parse new value JSON " + newValueJsonString + " : " + e;
851 resolvedValue = this._resolveCallArgument(newValueJson);
856 setter(scopeNumber, variableName, resolvedValue);
858 return "Failed to change variable value: " + e;
864 * @param {!Object} topCallFrame
865 * @param {string} callFrameId
868 _callFrameForId: function(topCallFrame, callFrameId)
870 var parsedCallFrameId = nullifyObjectProto(InjectedScriptHost.eval("(" + callFrameId + ")"));
871 return this._callFrameForParsedId(topCallFrame, parsedCallFrameId, []);
875 * @param {!Object} topCallFrame
876 * @param {!Object} parsedCallFrameId
877 * @param {!Array.<!Object>} asyncCallStacks
880 _callFrameForParsedId: function(topCallFrame, parsedCallFrameId, asyncCallStacks)
882 var asyncOrdinal = parsedCallFrameId["asyncOrdinal"]; // 1-based index
884 topCallFrame = asyncCallStacks[asyncOrdinal - 1];
885 var ordinal = parsedCallFrameId["ordinal"];
886 var callFrame = topCallFrame;
887 while (--ordinal >= 0 && callFrame)
888 callFrame = callFrame.caller;
893 * @param {!Object} objectId
894 * @return {!Object|symbol}
896 _objectForId: function(objectId)
898 return this._idToWrappedObject[objectId.id];
902 * @param {string} objectId
903 * @return {!Object|symbol}
905 findObjectById: function(objectId)
907 var parsedObjectId = this._parseObjectId(objectId);
908 return this._objectForId(parsedObjectId);
912 * @param {string} objectId
915 nodeForObjectId: function(objectId)
917 var object = this.findObjectById(objectId);
918 if (!object || this._subtype(object) !== "node")
920 return /** @type {!Node} */ (object);
924 * @param {string} name
927 module: function(name)
929 return this._modules[name];
933 * @param {string} name
934 * @param {string} source
937 injectModule: function(name, source)
939 delete this._modules[name];
940 var moduleFunction = InjectedScriptHost.eval("(" + source + ")");
941 if (typeof moduleFunction !== "function") {
942 inspectedWindow.console.error("Web Inspector error: A function was expected for module %s evaluation", name);
945 var module = InjectedScriptHost.callFunction(moduleFunction, inspectedWindow, [InjectedScriptHost, inspectedWindow, injectedScriptId, this]);
946 this._modules[name] = module;
954 _isDefined: function(object)
956 return !!object || this._isHTMLAllCollection(object);
963 _isHTMLAllCollection: function(object)
965 // document.all is reported as undefined, but we still want to process it.
966 return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object);
973 _subtype: function(obj)
978 if (this.isPrimitiveValue(obj))
981 var preciseType = InjectedScriptHost.type(obj);
985 if (isArrayLike(obj))
988 // If owning frame has navigated to somewhere else window properties will be undefined.
996 _describe: function(obj)
998 if (this.isPrimitiveValue(obj))
1001 var subtype = this._subtype(obj);
1003 if (subtype === "regexp")
1004 return toString(obj);
1006 if (subtype === "date")
1007 return toString(obj);
1009 if (subtype === "node") {
1010 var description = obj.nodeName.toLowerCase();
1011 switch (obj.nodeType) {
1012 case 1 /* Node.ELEMENT_NODE */:
1013 description += obj.id ? "#" + obj.id : "";
1014 var className = obj.className;
1015 description += (className && typeof className === "string") ? "." + className.trim().replace(/\s+/g, ".") : "";
1017 case 10 /*Node.DOCUMENT_TYPE_NODE */:
1018 description = "<!DOCTYPE " + description + ">";
1024 var className = InjectedScriptHost.internalConstructorName(obj);
1025 if (subtype === "array") {
1026 if (typeof obj.length === "number")
1027 className += "[" + obj.length + "]";
1031 // NodeList in JSC is a function, check for array prior to this.
1032 if (typeof obj === "function")
1033 return toString(obj);
1035 if (isSymbol(obj)) {
1037 return InjectedScriptHost.callFunction(Symbol.prototype.toString, obj) || "Symbol";
1043 if (obj instanceof Error && !!obj.message)
1044 return className + ": " + obj.message;
1051 * @type {!InjectedScript}
1054 var injectedScript = new InjectedScript();
1059 * @param {string=} objectGroupName
1060 * @param {boolean=} forceValueType
1061 * @param {boolean=} generatePreview
1062 * @param {?Array.<string>=} columnNames
1063 * @param {boolean=} isTable
1065 InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames, isTable)
1067 this.type = typeof object;
1068 if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
1069 // We don't send undefined values over JSON.
1070 if (this.type !== "undefined")
1071 this.value = object;
1073 // Null object is object with 'null' subtype.
1074 if (object === null)
1075 this.subtype = "null";
1077 // Provide user-friendly number values.
1078 if (this.type === "number") {
1079 this.description = toStringDescription(object);
1080 // Override "value" property for values that can not be JSON-stringified.
1081 switch (this.description) {
1086 this.value = this.description;
1094 object = /** @type {!Object} */ (object);
1096 this.objectId = injectedScript._bind(object, objectGroupName);
1097 var subtype = injectedScript._subtype(object);
1099 this.subtype = subtype;
1100 var className = InjectedScriptHost.internalConstructorName(object);
1102 this.className = className;
1103 this.description = injectedScript._describe(object);
1105 if (generatePreview && (this.type === "object" || injectedScript._isHTMLAllCollection(object)))
1106 this.preview = this._generatePreview(object, undefined, columnNames, isTable);
1109 InjectedScript.RemoteObject.prototype = {
1111 * @param {!Object} object
1112 * @param {?Array.<string>=} firstLevelKeys
1113 * @param {?Array.<string>=} secondLevelKeys
1114 * @param {boolean=} isTable
1115 * @return {!RuntimeAgent.ObjectPreview} preview
1117 _generatePreview: function(object, firstLevelKeys, secondLevelKeys, isTable)
1119 var preview = { __proto__: null };
1120 preview.lossless = true;
1121 preview.overflow = false;
1122 preview.properties = [];
1124 var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
1126 var propertiesThreshold = {
1127 properties: isTable ? 1000 : max(5, firstLevelKeysCount),
1128 indexes: isTable ? 1000 : max(100, firstLevelKeysCount)
1132 var descriptors = injectedScript._propertyDescriptors(object);
1134 if (firstLevelKeys) {
1135 var nameToDescriptors = { __proto__: null };
1136 for (var i = 0; i < descriptors.length; ++i) {
1137 var descriptor = descriptors[i];
1138 nameToDescriptors["#" + descriptor.name] = descriptor;
1141 for (var i = 0; i < firstLevelKeys.length; ++i)
1142 descriptors[i] = nameToDescriptors["#" + firstLevelKeys[i]];
1145 this._appendPropertyDescriptors(preview, descriptors, propertiesThreshold, secondLevelKeys, isTable);
1146 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
1149 // Add internal properties to preview.
1150 var internalProperties = InjectedScriptHost.getInternalProperties(object) || [];
1151 for (var i = 0; i < internalProperties.length; ++i) {
1152 internalProperties[i] = nullifyObjectProto(internalProperties[i]);
1153 internalProperties[i].enumerable = true;
1155 this._appendPropertyDescriptors(preview, internalProperties, propertiesThreshold, secondLevelKeys, isTable);
1158 preview.lossless = false;
1165 * @param {!RuntimeAgent.ObjectPreview} preview
1166 * @param {!Array.<!Object>} descriptors
1167 * @param {!Object} propertiesThreshold
1168 * @param {?Array.<string>=} secondLevelKeys
1169 * @param {boolean=} isTable
1171 _appendPropertyDescriptors: function(preview, descriptors, propertiesThreshold, secondLevelKeys, isTable)
1173 for (var i = 0; i < descriptors.length; ++i) {
1174 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
1177 var descriptor = descriptors[i];
1180 if (descriptor.wasThrown) {
1181 preview.lossless = false;
1184 if (!descriptor.enumerable && !descriptor.isOwn)
1187 var name = descriptor.name;
1188 if (name === "__proto__")
1190 if (this.subtype === "array" && name === "length")
1193 if (!("value" in descriptor)) {
1194 preview.lossless = false;
1195 this._appendPropertyPreview(preview, { name: name, type: "accessor", __proto__: null }, propertiesThreshold);
1199 var value = descriptor.value;
1200 if (value === null) {
1201 this._appendPropertyPreview(preview, { name: name, type: "object", value: "null", __proto__: null }, propertiesThreshold);
1205 const maxLength = 100;
1206 var type = typeof value;
1207 if (!descriptor.enumerable && type === "function")
1209 if (type === "undefined" && injectedScript._isHTMLAllCollection(value))
1212 if (InjectedScript.primitiveTypes[type]) {
1213 if (type === "string" && value.length > maxLength) {
1214 value = this._abbreviateString(value, maxLength, true);
1215 preview.lossless = false;
1217 this._appendPropertyPreview(preview, { name: name, type: type, value: toStringDescription(value), __proto__: null }, propertiesThreshold);
1221 if (secondLevelKeys === null || secondLevelKeys) {
1222 var subPreview = this._generatePreview(value, secondLevelKeys || undefined, undefined, isTable);
1223 var property = { name: name, type: type, valuePreview: subPreview, __proto__: null };
1224 this._appendPropertyPreview(preview, property, propertiesThreshold);
1225 if (!subPreview.lossless)
1226 preview.lossless = false;
1227 if (subPreview.overflow)
1228 preview.overflow = true;
1232 preview.lossless = false;
1234 var subtype = injectedScript._subtype(value);
1235 var description = "";
1236 if (type !== "function")
1237 description = this._abbreviateString(/** @type {string} */ (injectedScript._describe(value)), maxLength, subtype === "regexp");
1239 var property = { name: name, type: type, value: description, __proto__: null };
1241 property.subtype = subtype;
1242 this._appendPropertyPreview(preview, property, propertiesThreshold);
1247 * @param {!RuntimeAgent.ObjectPreview} preview
1248 * @param {!Object} property
1249 * @param {!Object} propertiesThreshold
1251 _appendPropertyPreview: function(preview, property, propertiesThreshold)
1253 if (toString(property.name >>> 0) === property.name)
1254 propertiesThreshold.indexes--;
1256 propertiesThreshold.properties--;
1257 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) {
1258 preview.overflow = true;
1259 preview.lossless = false;
1261 push(preview.properties, property);
1266 * @param {string} string
1267 * @param {number} maxLength
1268 * @param {boolean=} middle
1271 _abbreviateString: function(string, maxLength, middle)
1273 if (string.length <= maxLength)
1276 var leftHalf = maxLength >> 1;
1277 var rightHalf = maxLength - leftHalf - 1;
1278 return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf);
1280 return string.substr(0, maxLength) + "\u2026";
1287 * @param {number} ordinal
1288 * @param {!Object} callFrame
1289 * @param {number} asyncOrdinal
1291 InjectedScript.CallFrameProxy = function(ordinal, callFrame, asyncOrdinal)
1293 this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + (asyncOrdinal ? ",\"asyncOrdinal\":" + asyncOrdinal : "") + "}";
1294 this.functionName = (callFrame.type === "function" ? callFrame.functionName : "");
1295 this.location = { scriptId: toString(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column, __proto__: null };
1296 this.scopeChain = this._wrapScopeChain(callFrame);
1297 this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace");
1298 if (callFrame.isAtReturn)
1299 this.returnValue = injectedScript._wrapObject(callFrame.returnValue, "backtrace");
1302 InjectedScript.CallFrameProxy.prototype = {
1304 * @param {!Object} callFrame
1305 * @return {!Array.<!DebuggerAgent.Scope>}
1307 _wrapScopeChain: function(callFrame)
1309 var scopeChain = callFrame.scopeChain;
1310 var scopeChainProxy = [];
1311 for (var i = 0; i < scopeChain.length; ++i)
1312 scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace");
1313 return scopeChainProxy;
1320 * @param {number} scopeTypeCode
1321 * @param {*} scopeObject
1322 * @param {string} groupId
1323 * @return {!DebuggerAgent.Scope}
1325 InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId)
1327 const GLOBAL_SCOPE = 0;
1328 const LOCAL_SCOPE = 1;
1329 const WITH_SCOPE = 2;
1330 const CLOSURE_SCOPE = 3;
1331 const CATCH_SCOPE = 4;
1333 /** @type {!Object.<number, string>} */
1334 var scopeTypeNames = { __proto__: null };
1335 scopeTypeNames[GLOBAL_SCOPE] = "global";
1336 scopeTypeNames[LOCAL_SCOPE] = "local";
1337 scopeTypeNames[WITH_SCOPE] = "with";
1338 scopeTypeNames[CLOSURE_SCOPE] = "closure";
1339 scopeTypeNames[CATCH_SCOPE] = "catch";
1342 object: injectedScript._wrapObject(scopeObject, groupId),
1343 type: /** @type {!DebuggerAgent.ScopeType} */ (scopeTypeNames[scopeTypeCode]),
1350 * @param {!CommandLineAPIImpl} commandLineAPIImpl
1351 * @param {?Object} callFrame
1353 function CommandLineAPI(commandLineAPIImpl, callFrame)
1356 * @param {string} member
1359 function inScopeVariables(member)
1364 var scopeChain = callFrame.scopeChain;
1365 for (var i = 0; i < scopeChain.length; ++i) {
1366 if (member in scopeChain[i])
1373 * @param {string} name The name of the method for which a toString method should be generated.
1374 * @return {function():string}
1376 function customToStringMethod(name)
1380 var funcArgsSyntax = "";
1382 var funcSyntax = "" + commandLineAPIImpl[name];
1383 funcSyntax = funcSyntax.replace(/\n/g, " ");
1384 funcSyntax = funcSyntax.replace(/^function[^\(]*\(([^\)]*)\).*$/, "$1");
1385 funcSyntax = funcSyntax.replace(/\s*,\s*/g, ", ");
1386 funcSyntax = funcSyntax.replace(/\bopt_(\w+)\b/g, "[$1]");
1387 funcArgsSyntax = funcSyntax.trim();
1390 return "function " + name + "(" + funcArgsSyntax + ") { [Command Line API] }";
1394 for (var i = 0; i < CommandLineAPI.members_.length; ++i) {
1395 var member = CommandLineAPI.members_[i];
1396 if (member in inspectedWindow || inScopeVariables(member))
1399 this[member] = bind(commandLineAPIImpl[member], commandLineAPIImpl);
1400 this[member].toString = customToStringMethod(member);
1403 for (var i = 0; i < 5; ++i) {
1404 var member = "$" + i;
1405 if (member in inspectedWindow || inScopeVariables(member))
1408 this.__defineGetter__("$" + i, bind(commandLineAPIImpl._inspectedObject, commandLineAPIImpl, i));
1411 this.$_ = injectedScript._lastResult;
1413 this.__proto__ = null;
1416 // NOTE: Please keep the list of API methods below snchronized to that in WebInspector.RuntimeModel!
1417 // NOTE: Argument names of these methods will be printed in the console, so use pretty names!
1419 * @type {!Array.<string>}
1422 CommandLineAPI.members_ = [
1423 "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd",
1424 "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners",
1425 "debug", "undebug", "monitor", "unmonitor", "table"
1431 function CommandLineAPIImpl()
1435 CommandLineAPIImpl.prototype = {
1437 * @param {string} selector
1438 * @param {!Node=} opt_startNode
1441 $: function (selector, opt_startNode)
1443 if (this._canQuerySelectorOnNode(opt_startNode))
1444 return opt_startNode.querySelector(selector);
1446 return inspectedWindow.document.querySelector(selector);
1450 * @param {string} selector
1451 * @param {!Node=} opt_startNode
1454 $$: function (selector, opt_startNode)
1456 if (this._canQuerySelectorOnNode(opt_startNode))
1457 return opt_startNode.querySelectorAll(selector);
1458 return inspectedWindow.document.querySelectorAll(selector);
1462 * @param {!Node=} node
1465 _canQuerySelectorOnNode: function(node)
1467 return !!node && InjectedScriptHost.type(node) === "node" && (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE);
1471 * @param {string} xpath
1472 * @param {!Node=} opt_startNode
1475 $x: function(xpath, opt_startNode)
1477 var doc = (opt_startNode && opt_startNode.ownerDocument) || inspectedWindow.document;
1478 var result = doc.evaluate(xpath, opt_startNode || doc, null, XPathResult.ANY_TYPE, null);
1479 switch (result.resultType) {
1480 case XPathResult.NUMBER_TYPE:
1481 return result.numberValue;
1482 case XPathResult.STRING_TYPE:
1483 return result.stringValue;
1484 case XPathResult.BOOLEAN_TYPE:
1485 return result.booleanValue;
1489 while (node = result.iterateNext())
1498 dir: function(var_args)
1500 return InjectedScriptHost.callFunction(inspectedWindow.console.dir, inspectedWindow.console, slice(arguments));
1506 dirxml: function(var_args)
1508 return InjectedScriptHost.callFunction(inspectedWindow.console.dirxml, inspectedWindow.console, slice(arguments));
1512 * @return {!Array.<string>}
1514 keys: function(object)
1516 return Object.keys(object);
1520 * @return {!Array.<*>}
1522 values: function(object)
1525 for (var key in object)
1526 push(result, object[key]);
1533 profile: function(opt_title)
1535 return InjectedScriptHost.callFunction(inspectedWindow.console.profile, inspectedWindow.console, slice(arguments));
1541 profileEnd: function(opt_title)
1543 return InjectedScriptHost.callFunction(inspectedWindow.console.profileEnd, inspectedWindow.console, slice(arguments));
1547 * @param {!Object} object
1548 * @param {!Array.<string>|string=} opt_types
1550 monitorEvents: function(object, opt_types)
1552 if (!object || !object.addEventListener || !object.removeEventListener)
1554 var types = this._normalizeEventTypes(opt_types);
1555 for (var i = 0; i < types.length; ++i) {
1556 object.removeEventListener(types[i], this._logEvent, false);
1557 object.addEventListener(types[i], this._logEvent, false);
1562 * @param {!Object} object
1563 * @param {!Array.<string>|string=} opt_types
1565 unmonitorEvents: function(object, opt_types)
1567 if (!object || !object.addEventListener || !object.removeEventListener)
1569 var types = this._normalizeEventTypes(opt_types);
1570 for (var i = 0; i < types.length; ++i)
1571 object.removeEventListener(types[i], this._logEvent, false);
1578 inspect: function(object)
1580 return injectedScript._inspect(object);
1583 copy: function(object)
1586 if (injectedScript._subtype(object) === "node") {
1587 string = object.outerHTML;
1588 } else if (injectedScript.isPrimitiveValue(object)) {
1589 string = toString(object);
1592 string = JSON.stringify(object, null, " ");
1594 string = toString(object);
1598 var hints = { copyToClipboard: true, __proto__: null };
1599 var remoteObject = injectedScript._wrapObject(string, "")
1600 InjectedScriptHost.inspect(remoteObject, hints);
1605 InjectedScriptHost.clearConsoleMessages();
1609 * @param {!Node} node
1610 * @return {!{type: string, listener: function(), useCapture: boolean, remove: function()}|undefined}
1612 getEventListeners: function(node)
1614 var result = nullifyObjectProto(InjectedScriptHost.getEventListeners(node));
1617 /** @this {{type: string, listener: function(), useCapture: boolean}} */
1618 var removeFunc = function()
1620 node.removeEventListener(this.type, this.listener, this.useCapture);
1622 for (var type in result) {
1623 var listeners = result[type];
1624 for (var i = 0, listener; listener = listeners[i]; ++i) {
1625 listener["type"] = type;
1626 listener["remove"] = removeFunc;
1634 InjectedScriptHost.debugFunction(fn);
1637 undebug: function(fn)
1639 InjectedScriptHost.undebugFunction(fn);
1642 monitor: function(fn)
1644 InjectedScriptHost.monitorFunction(fn);
1647 unmonitor: function(fn)
1649 InjectedScriptHost.unmonitorFunction(fn);
1652 table: function(data, opt_columns)
1654 InjectedScriptHost.callFunction(inspectedWindow.console.table, inspectedWindow.console, slice(arguments));
1658 * @param {number} num
1660 _inspectedObject: function(num)
1662 return InjectedScriptHost.inspectedObject(num);
1666 * @param {!Array.<string>|string=} types
1667 * @return {!Array.<string>}
1669 _normalizeEventTypes: function(types)
1671 if (typeof types === "undefined")
1672 types = ["mouse", "key", "touch", "control", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll", "search", "devicemotion", "deviceorientation"];
1673 else if (typeof types === "string")
1677 for (var i = 0; i < types.length; ++i) {
1678 if (types[i] === "mouse")
1679 push(result, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout", "mousewheel");
1680 else if (types[i] === "key")
1681 push(result, "keydown", "keyup", "keypress", "textInput");
1682 else if (types[i] === "touch")
1683 push(result, "touchstart", "touchmove", "touchend", "touchcancel");
1684 else if (types[i] === "control")
1685 push(result, "resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset");
1687 push(result, types[i]);
1693 * @param {!Event} event
1695 _logEvent: function(event)
1697 inspectedWindow.console.log(event.type, event);
1701 injectedScript._commandLineAPIImpl = new CommandLineAPIImpl();
1702 return injectedScript;