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