/* * Copyright (C) 2007 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ (function (InjectedScriptHost, inspectedWindow, injectedScriptId) { function bind(thisObject, memberFunction) { var func = memberFunction; var args = Array.prototype.slice.call(arguments, 2); function bound() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))); } bound.toString = function() { return "bound: " + func; }; return bound; } var InjectedScript = function() { this._lastBoundObjectId = 1; this._idToWrappedObject = {}; this._idToObjectGroupName = {}; this._objectGroups = {}; } InjectedScript.prototype = { wrapObject: function(object, groupName, canAccessInspectedWindow) { if (canAccessInspectedWindow) return this._wrapObject(object, groupName); var result = {}; result.type = typeof object; result.description = this._toString(object); return result; }, inspectNode: function(object) { this._inspect(object); }, _inspect: function(object) { if (arguments.length === 0) return; var objectId = this._wrapObject(object, ""); var hints = {}; switch (injectedScript._describe(object)) { case "Database": var databaseId = InjectedScriptHost.databaseId(object) if (databaseId) hints.databaseId = databaseId; break; case "Storage": var storageId = InjectedScriptHost.storageId(object) if (storageId) hints.domStorageId = storageId; break; } InjectedScriptHost.inspect(objectId, hints); return object; }, _wrapObject: function(object, objectGroupName) { try { if (typeof object === "object" || typeof object === "function" || this._isHTMLAllCollection(object)) { var id = this._lastBoundObjectId++; this._idToWrappedObject[id] = object; var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}"; if (objectGroupName) { var group = this._objectGroups[objectGroupName]; if (!group) { group = []; this._objectGroups[objectGroupName] = group; } group.push(id); this._idToObjectGroupName[id] = objectGroupName; } } return InjectedScript.RemoteObject.fromObject(object, objectId); } catch (e) { return InjectedScript.RemoteObject.fromObject("[ Exception: " + e.toString() + " ]"); } }, _parseObjectId: function(objectId) { return eval("(" + objectId + ")"); }, releaseObjectGroup: function(objectGroupName) { var group = this._objectGroups[objectGroupName]; if (!group) return; for (var i = 0; i < group.length; i++) this._releaseObject(group[i]); delete this._objectGroups[objectGroupName]; }, dispatch: function(methodName, args) { var argsArray = eval("(" + args + ")"); var result = this[methodName].apply(this, argsArray); if (typeof result === "undefined") { inspectedWindow.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName); result = null; } return result; }, getProperties: function(objectId, ignoreHasOwnProperty) { var parsedObjectId = this._parseObjectId(objectId); var object = this._objectForId(parsedObjectId); var objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; if (!this._isDefined(object)) return false; var properties = []; var propertyNames = ignoreHasOwnProperty ? this._getPropertyNames(object) : Object.getOwnPropertyNames(object); if (!ignoreHasOwnProperty && object.__proto__) propertyNames.push("__proto__"); // Go over properties, prepare results. for (var i = 0; i < propertyNames.length; ++i) { var propertyName = propertyNames[i]; var property = {}; property.name = propertyName + ""; var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName); if (!isGetter) { try { property.value = this._wrapObject(object[propertyName], objectGroupName); } catch(e) { property.value = new InjectedScript.RemoteObject.fromException(e); } } else { // FIXME: this should show something like "getter" (bug 16734). property.value = new InjectedScript.RemoteObject.fromObject("\u2014"); // em dash property.isGetter = true; } properties.push(property); } return properties; }, setPropertyValue: function(objectId, propertyName, expression) { var parsedObjectId = this._parseObjectId(objectId); var object = this._objectForId(parsedObjectId); if (!this._isDefined(object)) return "Object with given id not found"; var expressionLength = expression.length; if (!expressionLength) { delete object[propertyName]; return propertyName in object ? "Cound not delete property." : undefined; } try { // Surround the expression in parenthesis so the result of the eval is the result // of the whole expression not the last potential sub-expression. // There is a regression introduced here: eval is now happening against global object, // not call frame while on a breakpoint. // TODO: bring evaluation against call frame back. var result = inspectedWindow.eval("(" + expression + ")"); // Store the result in the property. object[propertyName] = result; } catch(e) { try { var result = inspectedWindow.eval("\"" + expression.replace(/"/g, "\\\"") + "\""); object[propertyName] = result; } catch(e) { return e.toString(); } } }, releaseObject: function(objectId) { var parsedObjectId = this._parseObjectId(objectId); this._releaseObject(parsedObjectId.id); }, _releaseObject: function(id) { delete this._idToWrappedObject[id]; delete this._idToObjectGroupName[id]; }, _populatePropertyNames: function(object, resultSet) { for (var o = object; o; o = o.__proto__) { try { var names = Object.getOwnPropertyNames(o); for (var i = 0; i < names.length; ++i) resultSet[names[i]] = true; } catch (e) { } } }, _getPropertyNames: function(object, resultSet) { var propertyNameSet = {}; this._populatePropertyNames(object, propertyNameSet); return Object.keys(propertyNameSet); }, evaluate: function(expression, objectGroup, injectCommandLineAPI) { return this._evaluateAndWrap(inspectedWindow.eval, inspectedWindow, expression, objectGroup, false, injectCommandLineAPI); }, evaluateOn: function(objectId, expression) { var parsedObjectId = this._parseObjectId(objectId); var object = this._objectForId(parsedObjectId); if (!object) return "Could not find object with given id"; try { inspectedWindow.console._objectToEvaluateOn = object; return this._evaluateAndWrap(inspectedWindow.eval, inspectedWindow, "(function() {" + expression + "}).call(window.console._objectToEvaluateOn)", parsedObjectId.objectGroup, false, false); } finally { delete inspectedWindow.console._objectToEvaluateOn; } }, _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI) { try { return this._wrapObject(this._evaluateOn(evalFunction, object, expression, isEvalOnCallFrame, injectCommandLineAPI), objectGroup); } catch (e) { return InjectedScript.RemoteObject.fromException(e); } }, _evaluateOn: function(evalFunction, object, expression, isEvalOnCallFrame, injectCommandLineAPI) { // Only install command line api object for the time of evaluation. // Surround the expression in with statements to inject our command line API so that // the window object properties still take more precedent than our API functions. try { if (injectCommandLineAPI && inspectedWindow.console) { inspectedWindow.console._commandLineAPI = new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null); expression = "with ((window && window.console && window.console._commandLineAPI) || {}) {\n" + expression + "\n}"; } var value = evalFunction.call(object, expression); // When evaluating on call frame error is not thrown, but returned as a value. if (this._type(value) === "error") throw value.toString(); return value; } finally { if (injectCommandLineAPI && inspectedWindow.console) delete inspectedWindow.console._commandLineAPI; } }, callFrames: function() { var callFrame = InjectedScriptHost.currentCallFrame(); if (!callFrame) return false; var result = []; var depth = 0; do { result.push(new InjectedScript.CallFrameProxy(depth++, callFrame)); callFrame = callFrame.caller; } while (callFrame); return result; }, evaluateOnCallFrame: function(callFrameId, expression, objectGroup, injectCommandLineAPI) { var callFrame = this._callFrameForId(callFrameId); if (!callFrame) return "Could not find call frame with given id"; return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI); }, _callFrameForId: function(callFrameId) { var parsedCallFrameId = eval("(" + callFrameId + ")"); var ordinal = parsedCallFrameId.ordinal; var callFrame = InjectedScriptHost.currentCallFrame(); while (--ordinal >= 0 && callFrame) callFrame = callFrame.caller; return callFrame; }, _objectForId: function(objectId) { return this._idToWrappedObject[objectId.id]; }, nodeForObjectId: function(objectId) { var parsedObjectId = this._parseObjectId(objectId); var object = this._objectForId(parsedObjectId); if (!object || this._type(object) !== "node") return null; return object; }, _isDefined: function(object) { return object || this._isHTMLAllCollection(object); }, _isHTMLAllCollection: function(object) { // document.all is reported as undefined, but we still want to process it. return (typeof object === "undefined") && inspectedWindow.HTMLAllCollection && object instanceof inspectedWindow.HTMLAllCollection; }, _type: function(obj) { if (obj === null) return "null"; var type = typeof obj; if (type !== "object" && type !== "function") { // FIXME(33716): typeof document.all is always 'undefined'. if (this._isHTMLAllCollection(obj)) return "array"; return type; } // If owning frame has navigated to somewhere else window properties will be undefined. // In this case just return result of the typeof. if (!inspectedWindow.document) return type; if (obj instanceof inspectedWindow.Node) return (obj.nodeType === undefined ? type : "node"); if (obj instanceof inspectedWindow.String) return "string"; if (obj instanceof inspectedWindow.Array) return "array"; if (obj instanceof inspectedWindow.Boolean) return "boolean"; if (obj instanceof inspectedWindow.Number) return "number"; if (obj instanceof inspectedWindow.Date) return "date"; if (obj instanceof inspectedWindow.RegExp) return "regexp"; // FireBug's array detection. try { if (isFinite(obj.length) && typeof obj.splice === "function") return "array"; if (isFinite(obj.length) && typeof obj.callee === "function") // arguments. return "array"; } catch (e) { return type; } if (obj instanceof inspectedWindow.NodeList) return "array"; if (obj instanceof inspectedWindow.HTMLCollection) return "array"; if (obj instanceof inspectedWindow.Error) return "error"; return type; }, _describe: function(obj) { var type = this._type(obj); switch (type) { case "object": // Fall through. case "node": var result = InjectedScriptHost.internalConstructorName(obj); if (result === "Object") { // In Chromium DOM wrapper prototypes will have Object as their constructor name, // get the real DOM wrapper name from the constructor property. var constructorName = obj.constructor && obj.constructor.name; if (constructorName) return constructorName; } return result; case "array": var className = InjectedScriptHost.internalConstructorName(obj); if (typeof obj.length === "number") className += "[" + obj.length + "]"; return className; case "string": return obj; case "function": // Fall through. default: return this._toString(obj); } }, _toString: function(obj) { // We don't use String(obj) because inspectedWindow.String is undefined if owning frame navigated to another page. return "" + obj; } } var injectedScript = new InjectedScript(); InjectedScript.RemoteObject = function(objectId, type, description, hasChildren) { if (objectId) { this.objectId = objectId; this.hasChildren = hasChildren; } this.type = type; this.description = description; } InjectedScript.RemoteObject.fromException = function(e) { return new InjectedScript.RemoteObject(null, "error", e.toString()); } InjectedScript.RemoteObject.fromObject = function(object, objectId) { var type = injectedScript._type(object); var rawType = typeof object; var hasChildren = (rawType === "object" && object !== null && (!!Object.getOwnPropertyNames(object).length || !!object.__proto__)) || rawType === "function"; var description = ""; try { var description = injectedScript._describe(object); return new InjectedScript.RemoteObject(objectId, type, description, hasChildren); } catch (e) { return InjectedScript.RemoteObject.fromException(e); } } InjectedScript.CallFrameProxy = function(ordinal, callFrame) { this.id = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}"; this.functionName = (callFrame.type === "function" ? callFrame.functionName : ""); this.location = { sourceID: callFrame.sourceID, lineNumber: callFrame.line, columnNumber: callFrame.column }; this.scopeChain = this._wrapScopeChain(callFrame); } InjectedScript.CallFrameProxy.prototype = { _wrapScopeChain: function(callFrame) { const GLOBAL_SCOPE = 0; const LOCAL_SCOPE = 1; const WITH_SCOPE = 2; const CLOSURE_SCOPE = 3; const CATCH_SCOPE = 4; var scopeTypeNames = {}; scopeTypeNames[GLOBAL_SCOPE] = "global"; scopeTypeNames[LOCAL_SCOPE] = "local"; scopeTypeNames[WITH_SCOPE] = "with"; scopeTypeNames[CLOSURE_SCOPE] = "closure"; scopeTypeNames[CATCH_SCOPE] = "catch"; var scopeChain = callFrame.scopeChain; var scopeChainProxy = []; var foundLocalScope = false; for (var i = 0; i < scopeChain.length; i++) { var scope = {}; scope.object = injectedScript._wrapObject(scopeChain[i], "backtrace"); var scopeType = callFrame.scopeType(i); scope.type = scopeTypeNames[scopeType]; if (scopeType === LOCAL_SCOPE) scope.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace"); scopeChainProxy.push(scope); } return scopeChainProxy; } } function CommandLineAPI(commandLineAPIImpl, callFrame) { function inScopeVariables(member) { if (!callFrame) return false; var scopeChain = callFrame.scopeChain; for (var i = 0; i < scopeChain.length; ++i) { if (member in scopeChain[i]) return true; } return false; } for (var i = 0; i < CommandLineAPI.members_.length; ++i) { var member = CommandLineAPI.members_[i]; if (member in inspectedWindow || inScopeVariables(member)) continue; this[member] = bind(commandLineAPIImpl, commandLineAPIImpl[member]); } for (var i = 0; i < 5; ++i) { var member = "$" + i; if (member in inspectedWindow || inScopeVariables(member)) continue; this.__defineGetter__("$" + i, bind(commandLineAPIImpl, commandLineAPIImpl._inspectedNode, i)); } } CommandLineAPI.members_ = [ "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear" ]; function CommandLineAPIImpl() { } CommandLineAPIImpl.prototype = { $: function() { return document.getElementById.apply(document, arguments) }, $$: function() { return document.querySelectorAll.apply(document, arguments) }, $x: function(xpath, context) { var nodes = []; try { var doc = (context && context.ownerDocument) || inspectedWindow.document; var results = doc.evaluate(xpath, context || doc, null, XPathResult.ANY_TYPE, null); var node; while (node = results.iterateNext()) nodes.push(node); } catch (e) { } return nodes; }, dir: function() { return console.dir.apply(console, arguments) }, dirxml: function() { return console.dirxml.apply(console, arguments) }, keys: function(object) { return Object.keys(object); }, values: function(object) { var result = []; for (var key in object) result.push(object[key]); return result; }, profile: function() { return console.profile.apply(console, arguments) }, profileEnd: function() { return console.profileEnd.apply(console, arguments) }, monitorEvents: function(object, types) { if (!object || !object.addEventListener || !object.removeEventListener) return; types = this._normalizeEventTypes(types); for (var i = 0; i < types.length; ++i) { object.removeEventListener(types[i], this._logEvent, false); object.addEventListener(types[i], this._logEvent, false); } }, unmonitorEvents: function(object, types) { if (!object || !object.addEventListener || !object.removeEventListener) return; types = this._normalizeEventTypes(types); for (var i = 0; i < types.length; ++i) object.removeEventListener(types[i], this._logEvent, false); }, inspect: function(object) { return injectedScript._inspect(object); }, copy: function(object) { if (injectedScript._type(object) === "node") object = object.outerHTML; InjectedScriptHost.copyText(object); }, clear: function() { InjectedScriptHost.clearConsoleMessages(); }, _inspectedNode: function(num) { return InjectedScriptHost.inspectedNode(num); }, _normalizeEventTypes: function(types) { if (typeof types === "undefined") types = [ "mouse", "key", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll" ]; else if (typeof types === "string") types = [ types ]; var result = []; for (var i = 0; i < types.length; i++) { if (types[i] === "mouse") result.splice(0, 0, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout"); else if (types[i] === "key") result.splice(0, 0, "keydown", "keyup", "keypress"); else result.push(types[i]); } return result; }, _logEvent: function(event) { console.log(event.type, event); } } injectedScript._commandLineAPIImpl = new CommandLineAPIImpl(); return injectedScript; })