Web Inspector: Provide $exception in the console for the thrown exception value
[WebKit-https.git] / Source / WebCore / inspector / CommandLineAPIModuleSource.js
1 /*
2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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 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.
16  *
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.
27  */
28
29 //# sourceURL=__WebInspectorCommandLineAPIModuleSource__
30
31 /**
32  * @param {InjectedScriptHost} InjectedScriptHost
33  * @param {Window} inspectedWindow
34  * @param {number} injectedScriptId
35  * @param {InjectedScript} injectedScript
36  * @param {CommandLineAPIHost} CommandLineAPIHost
37  */
38 (function (InjectedScriptHost, inspectedWindow, injectedScriptId, injectedScript, CommandLineAPIHost) {
39
40 /**
41  * @param {Arguments} array
42  * @param {number=} index
43  * @return {Array.<*>}
44  */
45 function slice(array, index)
46 {
47     var result = [];
48     for (var i = index || 0; i < array.length; ++i)
49         result.push(array[i]);
50     return result;
51 }
52
53 /**
54  * Please use this bind, not the one from Function.prototype
55  * @param {function(...)} func
56  * @param {Object} thisObject
57  * @param {...number} var_args
58  */
59 function bind(func, thisObject, var_args)
60 {
61     var args = slice(arguments, 2);
62
63     /**
64      * @param {...number} var_args
65      */
66     function bound(var_args)
67     {
68         return func.apply(thisObject, args.concat(slice(arguments)));
69     }
70     bound.toString = function() {
71         return "bound: " + func;
72     };
73     return bound;
74 }
75
76 /**
77  * @constructor
78  * @param {CommandLineAPIImpl} commandLineAPIImpl
79  * @param {Object} callFrame
80  */
81 function CommandLineAPI(commandLineAPIImpl, callFrame)
82 {
83     /**
84      * @param {string} member
85      * @return {boolean}
86      */
87     function inScopeVariables(member)
88     {
89         if (!callFrame)
90             return false;
91
92         var scopeChain = callFrame.scopeChain;
93         for (var i = 0; i < scopeChain.length; ++i) {
94             if (member in scopeChain[i])
95                 return true;
96         }
97         return false;
98     }
99
100     /**
101      * @param {string} name The name of the method for which a toString method should be generated.
102      * @return {function():string}
103      */
104     function customToStringMethod(name)
105     {
106         return function () { return "function " + name + "() { [Command Line API] }"; };
107     }
108
109     for (var i = 0; i < CommandLineAPI.members_.length; ++i) {
110         var member = CommandLineAPI.members_[i];
111         if (member in inspectedWindow || inScopeVariables(member))
112             continue;
113
114         this[member] = bind(commandLineAPIImpl[member], commandLineAPIImpl);
115         this[member].toString = customToStringMethod(member);
116     }
117
118     for (var i = 0; i < 5; ++i) {
119         var member = "$" + i;
120         if (member in inspectedWindow || inScopeVariables(member))
121             continue;
122
123         this.__defineGetter__("$" + i, bind(commandLineAPIImpl._inspectedObject, commandLineAPIImpl, i));
124     }
125
126     this.$_ = injectedScript._lastResult;
127     this.$exception = injectedScript._exceptionValue;
128 }
129
130 /**
131  * @type {Array.<string>}
132  * @const
133  */
134 CommandLineAPI.members_ = [
135     "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd",
136     "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners"
137 ];
138
139 /**
140  * @constructor
141  */
142 function CommandLineAPIImpl()
143 {
144 }
145
146 CommandLineAPIImpl.prototype = {
147     /**
148      * @param {string} selector
149      * @param {Node=} start
150      */
151     $: function (selector, start)
152     {
153         if (this._canQuerySelectorOnNode(start))
154             return start.querySelector(selector);
155
156         var result = inspectedWindow.document.querySelector(selector);
157         if (result)
158             return result;
159         if (selector && selector[0] !== "#") {
160             result = inspectedWindow.document.getElementById(selector);
161             if (result) {
162                 inspectedWindow.console.warn("The console function $() has changed from $=getElementById(id) to $=querySelector(selector). You might try $(\"#%s\")", selector );
163                 return null;
164             }
165         }
166         return result;
167     },
168
169     /**
170      * @param {string} selector
171      * @param {Node=} start
172      */
173     $$: function (selector, start)
174     {
175         if (this._canQuerySelectorOnNode(start))
176             return start.querySelectorAll(selector);
177         return inspectedWindow.document.querySelectorAll(selector);
178     },
179
180     /**
181      * @param {Node=} node
182      * @return {boolean}
183      */
184     _canQuerySelectorOnNode: function(node)
185     {
186         return !!node && InjectedScriptHost.type(node) === "node" && (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE);
187     },
188
189     /**
190      * @param {string} xpath
191      * @param {Node=} context
192      */
193     $x: function(xpath, context)
194     {
195         var doc = (context && context.ownerDocument) || inspectedWindow.document;
196         var result = doc.evaluate(xpath, context || doc, null, XPathResult.ANY_TYPE, null);
197         switch (result.resultType) {
198         case XPathResult.NUMBER_TYPE:
199             return result.numberValue;
200         case XPathResult.STRING_TYPE:
201             return result.stringValue;
202         case XPathResult.BOOLEAN_TYPE:
203             return result.booleanValue;
204         default:
205             var nodes = [];
206             var node;
207             while (node = result.iterateNext())
208                 nodes.push(node);
209             return nodes;
210         }
211     },
212
213     dir: function()
214     {
215         return inspectedWindow.console.dir.apply(inspectedWindow.console, arguments)
216     },
217
218     dirxml: function()
219     {
220         return inspectedWindow.console.dirxml.apply(inspectedWindow.console, arguments)
221     },
222
223     keys: function(object)
224     {
225         return Object.keys(object);
226     },
227
228     values: function(object)
229     {
230         var result = [];
231         for (var key in object)
232             result.push(object[key]);
233         return result;
234     },
235
236     profile: function()
237     {
238         return inspectedWindow.console.profile.apply(inspectedWindow.console, arguments)
239     },
240
241     profileEnd: function()
242     {
243         return inspectedWindow.console.profileEnd.apply(inspectedWindow.console, arguments)
244     },
245
246     /**
247      * @param {Object} object
248      * @param {Array.<string>|string=} types
249      */
250     monitorEvents: function(object, types)
251     {
252         if (!object || !object.addEventListener || !object.removeEventListener)
253             return;
254         types = this._normalizeEventTypes(types);
255         for (var i = 0; i < types.length; ++i) {
256             object.removeEventListener(types[i], this._logEvent, false);
257             object.addEventListener(types[i], this._logEvent, false);
258         }
259     },
260
261     /**
262      * @param {Object} object
263      * @param {Array.<string>|string=} types
264      */
265     unmonitorEvents: function(object, types)
266     {
267         if (!object || !object.addEventListener || !object.removeEventListener)
268             return;
269         types = this._normalizeEventTypes(types);
270         for (var i = 0; i < types.length; ++i)
271             object.removeEventListener(types[i], this._logEvent, false);
272     },
273
274     /**
275      * @param {*} object
276      * @return {*}
277      */
278     inspect: function(object)
279     {
280         return this._inspect(object);
281     },
282
283     copy: function(object)
284     {
285         if (injectedScript._subtype(object) === "node")
286             object = object.outerHTML;
287         CommandLineAPIHost.copyText(object);
288     },
289
290     clear: function()
291     {
292         CommandLineAPIHost.clearConsoleMessages();
293     },
294
295     /**
296      * @param {Node} node
297      */
298     getEventListeners: function(node)
299     {
300         return CommandLineAPIHost.getEventListeners(node);
301     },
302
303     /**
304      * @param {number} num
305      */
306     _inspectedObject: function(num)
307     {
308         return CommandLineAPIHost.inspectedObject(num);
309     },
310
311     /**
312      * @param {Array.<string>|string=} types
313      * @return {Array.<string>}
314      */
315     _normalizeEventTypes: function(types)
316     {
317         if (typeof types === "undefined")
318             types = [ "mouse", "key", "touch", "control", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll", "search", "devicemotion", "deviceorientation" ];
319         else if (typeof types === "string")
320             types = [ types ];
321
322         var result = [];
323         for (var i = 0; i < types.length; i++) {
324             if (types[i] === "mouse")
325                 result.splice(0, 0, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout", "mousewheel");
326             else if (types[i] === "key")
327                 result.splice(0, 0, "keydown", "keyup", "keypress", "textInput");
328             else if (types[i] === "touch")
329                 result.splice(0, 0, "touchstart", "touchmove", "touchend", "touchcancel");
330             else if (types[i] === "control")
331                 result.splice(0, 0, "resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset");
332             else
333                 result.push(types[i]);
334         }
335         return result;
336     },
337
338     /**
339      * @param {Event} event
340      */
341     _logEvent: function(event)
342     {
343         inspectedWindow.console.log(event.type, event);
344     },
345
346     /**
347      * @param {*} object
348      * @return {*}
349      */
350     _inspect: function(object)
351     {
352         if (arguments.length === 0)
353             return;
354
355         var objectId = injectedScript._wrapObject(object, "");
356         var hints = {};
357
358         switch (injectedScript._describe(object)) {
359         case "Database":
360             var databaseId = CommandLineAPIHost.databaseId(object)
361             if (databaseId)
362                 hints.databaseId = databaseId;
363             break;
364         case "Storage":
365             var storageId = CommandLineAPIHost.storageId(object)
366             if (storageId)
367                 hints.domStorageId = InjectedScriptHost.evaluate("(" + storageId + ")");
368             break;
369         }
370
371         CommandLineAPIHost.inspect(objectId, hints);
372         return object;
373     }
374 }
375
376 injectedScript.CommandLineAPI = CommandLineAPI;
377 injectedScript._commandLineAPIImpl = new CommandLineAPIImpl();
378
379 // This Module doesn't expose an object, it just adds an extension that InjectedScript uses.
380 // However, we return an empty object, so that InjectedScript knows this module has been loaded.
381 return {};
382
383 })