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._idToObjectGroupName = {};
50 this._objectGroups = {};
53 InjectedScript.prototype = {
54 wrapObject: function(object, groupName, canAccessInspectedWindow)
56 if (canAccessInspectedWindow)
57 return this._wrapObject(object, groupName);
59 result.type = typeof object;
60 result.description = this._toString(object);
64 inspectNode: function(object)
66 this._inspect(object);
69 _inspect: function(object)
71 if (arguments.length === 0)
74 var objectId = this._wrapObject(object, "", false);
77 switch (injectedScript._describe(object)) {
79 var databaseId = InjectedScriptHost.databaseId(object)
81 hints.databaseId = databaseId;
84 var storageId = InjectedScriptHost.storageId(object)
86 hints.domStorageId = storageId;
89 InjectedScriptHost.inspect(objectId, hints);
93 _wrapObject: function(object, objectGroupName, abbreviate)
96 if (typeof object === "object" || typeof object === "function" || this._isHTMLAllCollection(object)) {
97 var id = this._lastBoundObjectId++;
98 this._idToWrappedObject[id] = object;
99 var objectId = { injectedScriptId: injectedScriptId, id: id };
101 if (objectGroupName) {
102 var group = this._objectGroups[objectGroupName];
105 this._objectGroups[objectGroupName] = group;
108 this._idToObjectGroupName[id] = objectGroupName;
111 return InjectedScript.RemoteObject.fromObject(object, objectId, abbreviate);
113 return InjectedScript.RemoteObject.fromObject("[ Exception: " + e.toString() + " ]");
117 _parseObjectId: function(objectId)
119 return eval("(" + objectId + ")");
122 releaseObjectGroup: function(objectGroupName)
124 var group = this._objectGroups[objectGroupName];
127 for (var i = 0; i < group.length; i++)
128 this._releaseObject(group[i]);
129 delete this._objectGroups[objectGroupName];
132 dispatch: function(methodName, args)
134 var argsArray = eval("(" + args + ")");
135 var result = this[methodName].apply(this, argsArray);
136 if (typeof result === "undefined") {
137 inspectedWindow.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
143 getProperties: function(objectId, ignoreHasOwnProperty, abbreviate)
145 var parsedObjectId = this._parseObjectId(objectId);
146 var object = this._objectForId(parsedObjectId);
147 var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
149 if (!this._isDefined(object))
153 var propertyNames = ignoreHasOwnProperty ? this._getPropertyNames(object) : Object.getOwnPropertyNames(object);
154 if (!ignoreHasOwnProperty && object.__proto__)
155 propertyNames.push("__proto__");
157 // Go over properties, prepare results.
158 for (var i = 0; i < propertyNames.length; ++i) {
159 var propertyName = propertyNames[i];
162 property.name = propertyName + "";
163 var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName);
166 property.value = this._wrapObject(object[propertyName], objectGroupName, abbreviate);
168 property.value = new InjectedScript.RemoteObject.fromException(e);
171 // FIXME: this should show something like "getter" (bug 16734).
172 property.value = new InjectedScript.RemoteObject.fromObject("\u2014"); // em dash
173 property.isGetter = true;
175 properties.push(property);
180 setPropertyValue: function(objectId, propertyName, expression)
182 var parsedObjectId = this._parseObjectId(objectId);
183 var object = this._objectForId(parsedObjectId);
184 if (!this._isDefined(object))
187 var expressionLength = expression.length;
188 if (!expressionLength) {
189 delete object[propertyName];
190 return !(propertyName in object);
194 // Surround the expression in parenthesis so the result of the eval is the result
195 // of the whole expression not the last potential sub-expression.
197 // There is a regression introduced here: eval is now happening against global object,
198 // not call frame while on a breakpoint.
199 // TODO: bring evaluation against call frame back.
200 var result = inspectedWindow.eval("(" + expression + ")");
201 // Store the result in the property.
202 object[propertyName] = result;
206 var result = inspectedWindow.eval("\"" + expression.replace(/"/g, "\\\"") + "\"");
207 object[propertyName] = result;
215 releaseObject: function(objectId)
217 var parsedObjectId = this._parseObjectId(objectId);
218 this._releaseObject(parsedObjectId.id);
221 _releaseObject: function(id)
223 delete this._idToWrappedObject[id];
224 delete this._idToObjectGroupName[id];
227 _populatePropertyNames: function(object, resultSet)
229 for (var o = object; o; o = o.__proto__) {
231 var names = Object.getOwnPropertyNames(o);
232 for (var i = 0; i < names.length; ++i)
233 resultSet[names[i]] = true;
239 _getPropertyNames: function(object, resultSet)
241 var propertyNameSet = {};
242 this._populatePropertyNames(object, propertyNameSet);
243 return Object.keys(propertyNameSet);
246 evaluate: function(expression, objectGroup, injectCommandLineAPI)
248 return this._evaluateAndWrap(inspectedWindow.eval, inspectedWindow, expression, objectGroup, false, injectCommandLineAPI);
251 evaluateOn: function(objectId, expression)
253 var parsedObjectId = this._parseObjectId(objectId);
254 var object = this._objectForId(parsedObjectId);
258 inspectedWindow.console._objectToEvaluateOn = object;
259 return this._evaluateAndWrap(inspectedWindow.eval, inspectedWindow, "(function() {" + expression + "}).call(window.console._objectToEvaluateOn)", parsedObjectId.objectGroup, false, false);
261 delete inspectedWindow.console._objectToEvaluateOn;
265 _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI)
268 return this._wrapObject(this._evaluateOn(evalFunction, object, expression, isEvalOnCallFrame, injectCommandLineAPI), objectGroup);
270 return InjectedScript.RemoteObject.fromException(e);
274 _evaluateOn: function(evalFunction, object, expression, isEvalOnCallFrame, injectCommandLineAPI)
276 // Only install command line api object for the time of evaluation.
277 // Surround the expression in with statements to inject our command line API so that
278 // the window object properties still take more precedent than our API functions.
281 if (injectCommandLineAPI && inspectedWindow.console) {
282 inspectedWindow.console._commandLineAPI = new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
283 expression = "with ((window && window.console && window.console._commandLineAPI) || {}) {\n" + expression + "\n}";
286 var value = evalFunction.call(object, expression);
288 // When evaluating on call frame error is not thrown, but returned as a value.
289 if (this._type(value) === "error")
290 throw value.toString();
294 if (injectCommandLineAPI && inspectedWindow.console)
295 delete inspectedWindow.console._commandLineAPI;
299 callFrames: function()
301 var callFrame = InjectedScriptHost.currentCallFrame();
305 injectedScript.releaseObjectGroup("backtrace");
309 result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
310 callFrame = callFrame.caller;
315 evaluateOnCallFrame: function(callFrameId, expression, objectGroup, injectCommandLineAPI)
317 var callFrame = this._callFrameForId(callFrameId);
320 return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI);
323 _callFrameForId: function(callFrameId)
325 var parsedCallFrameId = eval("(" + callFrameId + ")");
326 var ordinal = parsedCallFrameId.ordinal;
327 var callFrame = InjectedScriptHost.currentCallFrame();
328 while (--ordinal >= 0 && callFrame)
329 callFrame = callFrame.caller;
333 _objectForId: function(objectId)
335 return this._idToWrappedObject[objectId.id];
338 nodeForObjectId: function(objectId)
340 var parsedObjectId = this._parseObjectId(objectId);
341 var object = this._objectForId(parsedObjectId);
342 if (!object || this._type(object) !== "node")
347 resolveNode: function(node)
349 return this._wrapObject(node);
352 _isDefined: function(object)
354 return object || this._isHTMLAllCollection(object);
357 _isHTMLAllCollection: function(object)
359 // document.all is reported as undefined, but we still want to process it.
360 return (typeof object === "undefined") && inspectedWindow.HTMLAllCollection && object instanceof inspectedWindow.HTMLAllCollection;
368 var type = typeof obj;
369 if (type !== "object" && type !== "function") {
370 // FIXME(33716): typeof document.all is always 'undefined'.
371 if (this._isHTMLAllCollection(obj))
376 // If owning frame has navigated to somewhere else window properties will be undefined.
377 // In this case just return result of the typeof.
378 if (!inspectedWindow.document)
381 if (obj instanceof inspectedWindow.Node)
382 return (obj.nodeType === undefined ? type : "node");
383 if (obj instanceof inspectedWindow.String)
385 if (obj instanceof inspectedWindow.Array)
387 if (obj instanceof inspectedWindow.Boolean)
389 if (obj instanceof inspectedWindow.Number)
391 if (obj instanceof inspectedWindow.Date)
393 if (obj instanceof inspectedWindow.RegExp)
395 // FireBug's array detection.
396 if (isFinite(obj.length) && typeof obj.splice === "function")
398 if (isFinite(obj.length) && typeof obj.callee === "function") // arguments.
400 if (obj instanceof inspectedWindow.NodeList)
402 if (obj instanceof inspectedWindow.HTMLCollection)
404 if (obj instanceof inspectedWindow.Error)
409 _describe: function(obj, abbreviated)
411 var type = this._type(obj);
416 var result = InjectedScriptHost.internalConstructorName(obj);
417 if (result === "Object") {
418 // In Chromium DOM wrapper prototypes will have Object as their constructor name,
419 // get the real DOM wrapper name from the constructor property.
420 var constructorName = obj.constructor && obj.constructor.name;
422 return constructorName;
426 var className = InjectedScriptHost.internalConstructorName(obj);
427 if (typeof obj.length === "number")
428 className += "[" + obj.length + "]";
433 if (obj.length > 100)
434 return "\"" + obj.substring(0, 100) + "\u2026\"";
435 return "\"" + obj + "\"";
437 var objectText = this._toString(obj);
439 objectText = /.*/.exec(objectText)[0].replace(/ +$/g, "");
442 return this._toString(obj);
446 _toString: function(obj)
448 // We don't use String(obj) because inspectedWindow.String is undefined if owning frame navigated to another page.
453 var injectedScript = new InjectedScript();
455 InjectedScript.RemoteObject = function(objectId, type, description, hasChildren)
457 this.objectId = objectId;
459 this.description = description;
460 this.hasChildren = hasChildren;
463 InjectedScript.RemoteObject.fromException = function(e)
465 return new InjectedScript.RemoteObject(null, "error", e.toString());
468 InjectedScript.RemoteObject.fromObject = function(object, objectId, abbreviate)
470 var type = injectedScript._type(object);
471 var rawType = typeof object;
472 var hasChildren = (rawType === "object" && object !== null && (!!Object.getOwnPropertyNames(object).length || !!object.__proto__)) || rawType === "function";
473 var description = "";
475 var description = injectedScript._describe(object, abbreviate);
476 return new InjectedScript.RemoteObject(objectId, type, description, hasChildren);
478 return InjectedScript.RemoteObject.fromException(e);
482 InjectedScript.CallFrameProxy = function(ordinal, callFrame)
484 this.id = { ordinal: ordinal, injectedScriptId: injectedScriptId };
485 this.type = callFrame.type;
486 this.functionName = (this.type === "function" ? callFrame.functionName : "");
487 this.sourceID = callFrame.sourceID;
488 this.line = callFrame.line;
489 this.column = callFrame.column;
490 this.scopeChain = this._wrapScopeChain(callFrame);
493 InjectedScript.CallFrameProxy.prototype = {
494 _wrapScopeChain: function(callFrame)
496 const GLOBAL_SCOPE = 0;
497 const LOCAL_SCOPE = 1;
498 const WITH_SCOPE = 2;
499 const CLOSURE_SCOPE = 3;
500 const CATCH_SCOPE = 4;
502 var scopeChain = callFrame.scopeChain;
503 var scopeChainProxy = [];
504 var foundLocalScope = false;
505 for (var i = 0; i < scopeChain.length; i++) {
506 var scopeType = callFrame.scopeType(i);
507 var scopeObject = scopeChain[i];
508 var scopeObjectProxy = injectedScript._wrapObject(scopeObject, "backtrace", true);
512 foundLocalScope = true;
513 scopeObjectProxy.isLocal = true;
514 scopeObjectProxy.thisObject = injectedScript._wrapObject(callFrame.thisObject, "backtrace", true);
517 case CLOSURE_SCOPE: {
518 scopeObjectProxy.isClosure = true;
523 if (foundLocalScope && scopeObject instanceof inspectedWindow.Element)
524 scopeObjectProxy.isElement = true;
525 else if (foundLocalScope && scopeObject instanceof inspectedWindow.Document)
526 scopeObjectProxy.isDocument = true;
528 scopeObjectProxy.isWithBlock = true;
532 scopeChainProxy.push(scopeObjectProxy);
534 return scopeChainProxy;
538 function CommandLineAPI(commandLineAPIImpl, callFrame)
540 function inScopeVariables(member)
545 var scopeChain = callFrame.scopeChain;
546 for (var i = 0; i < scopeChain.length; ++i) {
547 if (member in scopeChain[i])
553 for (var i = 0; i < CommandLineAPI.members_.length; ++i) {
554 var member = CommandLineAPI.members_[i];
555 if (member in inspectedWindow || inScopeVariables(member))
558 this[member] = bind(commandLineAPIImpl, commandLineAPIImpl[member]);
561 for (var i = 0; i < 5; ++i) {
562 var member = "$" + i;
563 if (member in inspectedWindow || inScopeVariables(member))
566 this.__defineGetter__("$" + i, bind(commandLineAPIImpl, commandLineAPIImpl._inspectedNode, i));
570 CommandLineAPI.members_ = [
571 "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd",
572 "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear"
575 function CommandLineAPIImpl()
579 CommandLineAPIImpl.prototype = {
582 return document.getElementById.apply(document, arguments)
587 return document.querySelectorAll.apply(document, arguments)
590 $x: function(xpath, context)
594 var doc = (context && context.ownerDocument) || inspectedWindow.document;
595 var results = doc.evaluate(xpath, context || doc, null, XPathResult.ANY_TYPE, null);
597 while (node = results.iterateNext())
606 return console.dir.apply(console, arguments)
611 return console.dirxml.apply(console, arguments)
614 keys: function(object)
616 return Object.keys(object);
619 values: function(object)
622 for (var key in object)
623 result.push(object[key]);
629 return console.profile.apply(console, arguments)
632 profileEnd: function()
634 return console.profileEnd.apply(console, arguments)
637 monitorEvents: function(object, types)
639 if (!object || !object.addEventListener || !object.removeEventListener)
641 types = this._normalizeEventTypes(types);
642 for (var i = 0; i < types.length; ++i) {
643 object.removeEventListener(types[i], this._logEvent, false);
644 object.addEventListener(types[i], this._logEvent, false);
648 unmonitorEvents: function(object, types)
650 if (!object || !object.addEventListener || !object.removeEventListener)
652 types = this._normalizeEventTypes(types);
653 for (var i = 0; i < types.length; ++i)
654 object.removeEventListener(types[i], this._logEvent, false);
657 inspect: function(object)
659 return injectedScript._inspect(object);
662 copy: function(object)
664 if (injectedScript._type(object) === "node")
665 object = object.outerHTML;
666 InjectedScriptHost.copyText(object);
671 InjectedScriptHost.clearConsoleMessages();
674 _inspectedNode: function(num)
676 return InjectedScriptHost.inspectedNode(num);
679 _normalizeEventTypes: function(types)
681 if (typeof types === "undefined")
682 types = [ "mouse", "key", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll" ];
683 else if (typeof types === "string")
687 for (var i = 0; i < types.length; i++) {
688 if (types[i] === "mouse")
689 result.splice(0, 0, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout");
690 else if (types[i] === "key")
691 result.splice(0, 0, "keydown", "keyup", "keypress");
693 result.push(types[i]);
698 _logEvent: function(event)
700 console.log(event.type, event);
704 injectedScript._commandLineAPIImpl = new CommandLineAPIImpl();
705 return injectedScript;