2 * Copyright (C) 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 (function (InjectedScriptHost, inspectedWindow, injectedScriptId) {
31 function bind(thisObject, memberFunction)
33 var func = memberFunction;
34 var args = Array.prototype.slice.call(arguments, 2);
37 return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0)));
39 bound.toString = function() {
40 return "bound: " + func;
45 var InjectedScript = function()
47 this._lastBoundObjectId = 1;
48 this._idToWrappedObject = {};
49 this._objectGroups = {};
52 InjectedScript.prototype = {
53 wrapObject: function(object, groupName, canAccessInspectedWindow)
55 if (canAccessInspectedWindow)
56 return this._wrapObject(object, groupName);
58 result.type = typeof object;
59 result.description = this._toString(object);
63 inspectNode: function(object)
65 this._inspect(object);
68 _inspect: function(object)
70 if (arguments.length === 0)
73 var objectId = this._wrapObject(object, "", false);
76 switch (injectedScript._describe(object)) {
78 var databaseId = InjectedScriptHost.databaseId(object)
80 hints.databaseId = databaseId;
83 var storageId = InjectedScriptHost.storageId(object)
85 hints.domStorageId = storageId;
88 InjectedScriptHost.inspect(objectId, hints);
92 _wrapObject: function(object, objectGroupName, abbreviate)
95 if (typeof object === "object" || typeof object === "function" || this._isHTMLAllCollection(object)) {
96 var id = this._lastBoundObjectId++;
97 this._idToWrappedObject[id] = object;
98 var objectId = { injectedScriptId: injectedScriptId, id: id };
100 if (objectGroupName) {
101 var group = this._objectGroups[objectGroupName];
104 this._objectGroups[objectGroupName] = group;
107 objectId.groupName = objectGroupName;
110 return InjectedScript.RemoteObject.fromObject(object, objectId, abbreviate);
112 return InjectedScript.RemoteObject.fromObject("[ Exception: " + e.toString() + " ]");
116 _parseObjectId: function(objectId)
118 return eval("(" + objectId + ")");
121 releaseObjectGroup: function(objectGroupName)
123 var group = this._objectGroups[objectGroupName];
126 for (var i = 0; i < group.length; i++)
127 delete this._idToWrappedObject[group[i]];
128 delete this._objectGroups[objectGroupName];
131 dispatch: function(methodName, args)
133 var argsArray = eval("(" + args + ")");
134 var result = this[methodName].apply(this, argsArray);
135 if (typeof result === "undefined") {
136 inspectedWindow.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
142 getProperties: function(objectId, ignoreHasOwnProperty, abbreviate)
144 var parsedObjectId = this._parseObjectId(objectId);
145 var object = this._objectForId(parsedObjectId);
147 if (!this._isDefined(object))
151 var propertyNames = ignoreHasOwnProperty ? this._getPropertyNames(object) : Object.getOwnPropertyNames(object);
152 if (!ignoreHasOwnProperty && object.__proto__)
153 propertyNames.push("__proto__");
155 // Go over properties, prepare results.
156 for (var i = 0; i < propertyNames.length; ++i) {
157 var propertyName = propertyNames[i];
160 property.name = propertyName + "";
161 var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName);
164 property.value = this._wrapObject(object[propertyName], parsedObjectId.groupName, abbreviate);
166 property.value = new InjectedScript.RemoteObject.fromException(e);
169 // FIXME: this should show something like "getter" (bug 16734).
170 property.value = new InjectedScript.RemoteObject.fromObject("\u2014"); // em dash
171 property.isGetter = true;
173 properties.push(property);
178 setPropertyValue: function(objectId, propertyName, expression)
180 var parsedObjectId = this._parseObjectId(objectId);
181 var object = this._objectForId(parsedObjectId);
182 if (!this._isDefined(object))
185 var expressionLength = expression.length;
186 if (!expressionLength) {
187 delete object[propertyName];
188 return !(propertyName in object);
192 // Surround the expression in parenthesis so the result of the eval is the result
193 // of the whole expression not the last potential sub-expression.
195 // There is a regression introduced here: eval is now happening against global object,
196 // not call frame while on a breakpoint.
197 // TODO: bring evaluation against call frame back.
198 var result = inspectedWindow.eval("(" + expression + ")");
199 // Store the result in the property.
200 object[propertyName] = result;
204 var result = inspectedWindow.eval("\"" + expression.replace(/"/g, "\\\"") + "\"");
205 object[propertyName] = result;
213 releaseObject: function(objectId)
215 var parsedObjectId = this._parseObjectId(objectId);
216 delete this._idToWrappedObject[parsedObjectId.id];
219 _populatePropertyNames: function(object, resultSet)
221 for (var o = object; o; o = o.__proto__) {
223 var names = Object.getOwnPropertyNames(o);
224 for (var i = 0; i < names.length; ++i)
225 resultSet[names[i]] = true;
231 _getPropertyNames: function(object, resultSet)
233 var propertyNameSet = {};
234 this._populatePropertyNames(object, propertyNameSet);
235 return Object.keys(propertyNameSet);
238 evaluate: function(expression, objectGroup, injectCommandLineAPI)
240 return this._evaluateAndWrap(inspectedWindow.eval, inspectedWindow, expression, objectGroup, false, injectCommandLineAPI);
243 evaluateOn: function(objectId, expression)
245 var parsedObjectId = this._parseObjectId(objectId);
246 var object = this._objectForId(parsedObjectId);
250 inspectedWindow.console._objectToEvaluateOn = object;
251 return this._evaluateAndWrap(inspectedWindow.eval, inspectedWindow, "(function() {" + expression + "}).call(window.console._objectToEvaluateOn)", parsedObjectId.objectGroup, false, false);
253 delete inspectedWindow.console._objectToEvaluateOn;
257 _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI)
260 return this._wrapObject(this._evaluateOn(evalFunction, object, expression, isEvalOnCallFrame, injectCommandLineAPI), objectGroup);
262 return InjectedScript.RemoteObject.fromException(e);
266 _evaluateOn: function(evalFunction, object, expression, isEvalOnCallFrame, injectCommandLineAPI)
268 // Only install command line api object for the time of evaluation.
269 // Surround the expression in with statements to inject our command line API so that
270 // the window object properties still take more precedent than our API functions.
273 if (injectCommandLineAPI && inspectedWindow.console) {
274 inspectedWindow.console._commandLineAPI = new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
275 expression = "with ((window && window.console && window.console._commandLineAPI) || {}) {\n" + expression + "\n}";
278 var value = evalFunction.call(object, expression);
280 // When evaluating on call frame error is not thrown, but returned as a value.
281 if (this._type(value) === "error")
282 throw value.toString();
286 if (injectCommandLineAPI && inspectedWindow.console)
287 delete inspectedWindow.console._commandLineAPI;
291 callFrames: function()
293 var callFrame = InjectedScriptHost.currentCallFrame();
297 injectedScript.releaseObjectGroup("backtrace");
301 result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
302 callFrame = callFrame.caller;
307 evaluateOnCallFrame: function(callFrameId, expression, objectGroup, injectCommandLineAPI)
309 var callFrame = this._callFrameForId(callFrameId);
312 return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI);
315 _callFrameForId: function(callFrameId)
317 var parsedCallFrameId = eval("(" + callFrameId + ")");
318 var ordinal = parsedCallFrameId.ordinal;
319 var callFrame = InjectedScriptHost.currentCallFrame();
320 while (--ordinal >= 0 && callFrame)
321 callFrame = callFrame.caller;
325 _objectForId: function(objectId)
327 return this._idToWrappedObject[objectId.id];
330 nodeForObjectId: function(objectId)
332 var parsedObjectId = this._parseObjectId(objectId);
333 var object = this._objectForId(parsedObjectId);
334 if (!object || this._type(object) !== "node")
339 resolveNode: function(node)
341 return this._wrapObject(node);
344 _isDefined: function(object)
346 return object || this._isHTMLAllCollection(object);
349 _isHTMLAllCollection: function(object)
351 // document.all is reported as undefined, but we still want to process it.
352 return (typeof object === "undefined") && inspectedWindow.HTMLAllCollection && object instanceof inspectedWindow.HTMLAllCollection;
360 var type = typeof obj;
361 if (type !== "object" && type !== "function") {
362 // FIXME(33716): typeof document.all is always 'undefined'.
363 if (this._isHTMLAllCollection(obj))
368 // If owning frame has navigated to somewhere else window properties will be undefined.
369 // In this case just return result of the typeof.
370 if (!inspectedWindow.document)
373 if (obj instanceof inspectedWindow.Node)
374 return (obj.nodeType === undefined ? type : "node");
375 if (obj instanceof inspectedWindow.String)
377 if (obj instanceof inspectedWindow.Array)
379 if (obj instanceof inspectedWindow.Boolean)
381 if (obj instanceof inspectedWindow.Number)
383 if (obj instanceof inspectedWindow.Date)
385 if (obj instanceof inspectedWindow.RegExp)
387 // FireBug's array detection.
388 if (isFinite(obj.length) && typeof obj.splice === "function")
390 if (isFinite(obj.length) && typeof obj.callee === "function") // arguments.
392 if (obj instanceof inspectedWindow.NodeList)
394 if (obj instanceof inspectedWindow.HTMLCollection)
396 if (obj instanceof inspectedWindow.Error)
401 _describe: function(obj, abbreviated)
403 var type = this._type(obj);
408 var result = InjectedScriptHost.internalConstructorName(obj);
409 if (result === "Object") {
410 // In Chromium DOM wrapper prototypes will have Object as their constructor name,
411 // get the real DOM wrapper name from the constructor property.
412 var constructorName = obj.constructor && obj.constructor.name;
414 return constructorName;
418 var className = InjectedScriptHost.internalConstructorName(obj);
419 if (typeof obj.length === "number")
420 className += "[" + obj.length + "]";
425 if (obj.length > 100)
426 return "\"" + obj.substring(0, 100) + "\u2026\"";
427 return "\"" + obj + "\"";
429 var objectText = this._toString(obj);
431 objectText = /.*/.exec(objectText)[0].replace(/ +$/g, "");
434 return this._toString(obj);
438 _toString: function(obj)
440 // We don't use String(obj) because inspectedWindow.String is undefined if owning frame navigated to another page.
445 var injectedScript = new InjectedScript();
447 InjectedScript.RemoteObject = function(objectId, type, description, hasChildren)
449 this.objectId = objectId;
451 this.description = description;
452 this.hasChildren = hasChildren;
455 InjectedScript.RemoteObject.fromException = function(e)
457 return new InjectedScript.RemoteObject(null, "error", e.toString());
460 InjectedScript.RemoteObject.fromObject = function(object, objectId, abbreviate)
462 var type = injectedScript._type(object);
463 var rawType = typeof object;
464 var hasChildren = (rawType === "object" && object !== null && (!!Object.getOwnPropertyNames(object).length || !!object.__proto__)) || rawType === "function";
465 var description = "";
467 var description = injectedScript._describe(object, abbreviate);
468 return new InjectedScript.RemoteObject(objectId, type, description, hasChildren);
470 return InjectedScript.RemoteObject.fromException(e);
474 InjectedScript.CallFrameProxy = function(ordinal, callFrame)
476 this.id = { ordinal: ordinal, injectedScriptId: injectedScriptId };
477 this.type = callFrame.type;
478 this.functionName = (this.type === "function" ? callFrame.functionName : "");
479 this.sourceID = callFrame.sourceID;
480 this.line = callFrame.line;
481 this.column = callFrame.column;
482 this.scopeChain = this._wrapScopeChain(callFrame);
485 InjectedScript.CallFrameProxy.prototype = {
486 _wrapScopeChain: function(callFrame)
488 const GLOBAL_SCOPE = 0;
489 const LOCAL_SCOPE = 1;
490 const WITH_SCOPE = 2;
491 const CLOSURE_SCOPE = 3;
492 const CATCH_SCOPE = 4;
494 var scopeChain = callFrame.scopeChain;
495 var scopeChainProxy = [];
496 var foundLocalScope = false;
497 for (var i = 0; i < scopeChain.length; i++) {
498 var scopeType = callFrame.scopeType(i);
499 var scopeObject = scopeChain[i];
500 var scopeObjectProxy = injectedScript._wrapObject(scopeObject, "backtrace", true);
504 foundLocalScope = true;
505 scopeObjectProxy.isLocal = true;
506 scopeObjectProxy.thisObject = injectedScript._wrapObject(callFrame.thisObject, "backtrace", true);
509 case CLOSURE_SCOPE: {
510 scopeObjectProxy.isClosure = true;
515 if (foundLocalScope && scopeObject instanceof inspectedWindow.Element)
516 scopeObjectProxy.isElement = true;
517 else if (foundLocalScope && scopeObject instanceof inspectedWindow.Document)
518 scopeObjectProxy.isDocument = true;
520 scopeObjectProxy.isWithBlock = true;
524 scopeChainProxy.push(scopeObjectProxy);
526 return scopeChainProxy;
530 function CommandLineAPI(commandLineAPIImpl, callFrame)
532 function inScopeVariables(member)
537 var scopeChain = callFrame.scopeChain;
538 for (var i = 0; i < scopeChain.length; ++i) {
539 if (member in scopeChain[i])
545 for (var i = 0; i < CommandLineAPI.members_.length; ++i) {
546 var member = CommandLineAPI.members_[i];
547 if (member in inspectedWindow || inScopeVariables(member))
550 this[member] = bind(commandLineAPIImpl, commandLineAPIImpl[member]);
553 for (var i = 0; i < 5; ++i) {
554 var member = "$" + i;
555 if (member in inspectedWindow || inScopeVariables(member))
558 this.__defineGetter__("$" + i, bind(commandLineAPIImpl, commandLineAPIImpl._inspectedNode, i));
562 CommandLineAPI.members_ = [
563 "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd",
564 "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear"
567 function CommandLineAPIImpl()
571 CommandLineAPIImpl.prototype = {
574 return document.getElementById.apply(document, arguments)
579 return document.querySelectorAll.apply(document, arguments)
582 $x: function(xpath, context)
586 var doc = (context && context.ownerDocument) || inspectedWindow.document;
587 var results = doc.evaluate(xpath, context || doc, null, XPathResult.ANY_TYPE, null);
589 while (node = results.iterateNext())
598 return console.dir.apply(console, arguments)
603 return console.dirxml.apply(console, arguments)
606 keys: function(object)
608 return Object.keys(object);
611 values: function(object)
614 for (var key in object)
615 result.push(object[key]);
621 return console.profile.apply(console, arguments)
624 profileEnd: function()
626 return console.profileEnd.apply(console, arguments)
629 monitorEvents: function(object, types)
631 if (!object || !object.addEventListener || !object.removeEventListener)
633 types = this._normalizeEventTypes(types);
634 for (var i = 0; i < types.length; ++i) {
635 object.removeEventListener(types[i], this._logEvent, false);
636 object.addEventListener(types[i], this._logEvent, false);
640 unmonitorEvents: function(object, types)
642 if (!object || !object.addEventListener || !object.removeEventListener)
644 types = this._normalizeEventTypes(types);
645 for (var i = 0; i < types.length; ++i)
646 object.removeEventListener(types[i], this._logEvent, false);
649 inspect: function(object)
651 return injectedScript._inspect(object);
654 copy: function(object)
656 if (injectedScript._type(object) === "node")
657 object = object.outerHTML;
658 InjectedScriptHost.copyText(object);
663 InjectedScriptHost.clearConsoleMessages();
666 _inspectedNode: function(num)
668 return InjectedScriptHost.inspectedNode(num);
671 _normalizeEventTypes: function(types)
673 if (typeof types === "undefined")
674 types = [ "mouse", "key", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll" ];
675 else if (typeof types === "string")
679 for (var i = 0; i < types.length; i++) {
680 if (types[i] === "mouse")
681 result.splice(0, 0, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout");
682 else if (types[i] === "key")
683 result.splice(0, 0, "keydown", "keyup", "keypress");
685 result.push(types[i]);
690 _logEvent: function(event)
692 console.log(event.type, event);
696 injectedScript._commandLineAPIImpl = new CommandLineAPIImpl();
697 return injectedScript;