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