|
1 /* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ |
|
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 "use strict"; |
|
8 |
|
9 const {Cc, Ci, Cu, components} = require("chrome"); |
|
10 |
|
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
12 |
|
13 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm"); |
|
14 loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm"); |
|
15 |
|
16 // TODO: Bug 842672 - toolkit/ imports modules from browser/. |
|
17 // Note that these are only used in JSTermHelpers, see $0 and pprint(). |
|
18 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); |
|
19 loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm"); |
|
20 loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm"); |
|
21 loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm"); |
|
22 |
|
23 // Match the function name from the result of toString() or toSource(). |
|
24 // |
|
25 // Examples: |
|
26 // (function foobar(a, b) { ... |
|
27 // function foobar2(a) { ... |
|
28 // function() { ... |
|
29 const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/; |
|
30 |
|
31 // Match the function arguments from the result of toString() or toSource(). |
|
32 const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/; |
|
33 |
|
34 let WebConsoleUtils = { |
|
35 /** |
|
36 * Convenience function to unwrap a wrapped object. |
|
37 * |
|
38 * @param aObject the object to unwrap. |
|
39 * @return aObject unwrapped. |
|
40 */ |
|
41 unwrap: function WCU_unwrap(aObject) |
|
42 { |
|
43 try { |
|
44 return XPCNativeWrapper.unwrap(aObject); |
|
45 } |
|
46 catch (ex) { |
|
47 return aObject; |
|
48 } |
|
49 }, |
|
50 |
|
51 /** |
|
52 * Wrap a string in an nsISupportsString object. |
|
53 * |
|
54 * @param string aString |
|
55 * @return nsISupportsString |
|
56 */ |
|
57 supportsString: function WCU_supportsString(aString) |
|
58 { |
|
59 let str = Cc["@mozilla.org/supports-string;1"]. |
|
60 createInstance(Ci.nsISupportsString); |
|
61 str.data = aString; |
|
62 return str; |
|
63 }, |
|
64 |
|
65 /** |
|
66 * Clone an object. |
|
67 * |
|
68 * @param object aObject |
|
69 * The object you want cloned. |
|
70 * @param boolean aRecursive |
|
71 * Tells if you want to dig deeper into the object, to clone |
|
72 * recursively. |
|
73 * @param function [aFilter] |
|
74 * Optional, filter function, called for every property. Three |
|
75 * arguments are passed: key, value and object. Return true if the |
|
76 * property should be added to the cloned object. Return false to skip |
|
77 * the property. |
|
78 * @return object |
|
79 * The cloned object. |
|
80 */ |
|
81 cloneObject: function WCU_cloneObject(aObject, aRecursive, aFilter) |
|
82 { |
|
83 if (typeof aObject != "object") { |
|
84 return aObject; |
|
85 } |
|
86 |
|
87 let temp; |
|
88 |
|
89 if (Array.isArray(aObject)) { |
|
90 temp = []; |
|
91 Array.forEach(aObject, function(aValue, aIndex) { |
|
92 if (!aFilter || aFilter(aIndex, aValue, aObject)) { |
|
93 temp.push(aRecursive ? WCU_cloneObject(aValue) : aValue); |
|
94 } |
|
95 }); |
|
96 } |
|
97 else { |
|
98 temp = {}; |
|
99 for (let key in aObject) { |
|
100 let value = aObject[key]; |
|
101 if (aObject.hasOwnProperty(key) && |
|
102 (!aFilter || aFilter(key, value, aObject))) { |
|
103 temp[key] = aRecursive ? WCU_cloneObject(value) : value; |
|
104 } |
|
105 } |
|
106 } |
|
107 |
|
108 return temp; |
|
109 }, |
|
110 |
|
111 /** |
|
112 * Copies certain style attributes from one element to another. |
|
113 * |
|
114 * @param nsIDOMNode aFrom |
|
115 * The target node. |
|
116 * @param nsIDOMNode aTo |
|
117 * The destination node. |
|
118 */ |
|
119 copyTextStyles: function WCU_copyTextStyles(aFrom, aTo) |
|
120 { |
|
121 let win = aFrom.ownerDocument.defaultView; |
|
122 let style = win.getComputedStyle(aFrom); |
|
123 aTo.style.fontFamily = style.getPropertyCSSValue("font-family").cssText; |
|
124 aTo.style.fontSize = style.getPropertyCSSValue("font-size").cssText; |
|
125 aTo.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText; |
|
126 aTo.style.fontStyle = style.getPropertyCSSValue("font-style").cssText; |
|
127 }, |
|
128 |
|
129 /** |
|
130 * Gets the ID of the inner window of this DOM window. |
|
131 * |
|
132 * @param nsIDOMWindow aWindow |
|
133 * @return integer |
|
134 * Inner ID for the given aWindow. |
|
135 */ |
|
136 getInnerWindowId: function WCU_getInnerWindowId(aWindow) |
|
137 { |
|
138 return aWindow.QueryInterface(Ci.nsIInterfaceRequestor). |
|
139 getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; |
|
140 }, |
|
141 |
|
142 /** |
|
143 * Recursively gather a list of inner window ids given a |
|
144 * top level window. |
|
145 * |
|
146 * @param nsIDOMWindow aWindow |
|
147 * @return Array |
|
148 * list of inner window ids. |
|
149 */ |
|
150 getInnerWindowIDsForFrames: function WCU_getInnerWindowIDsForFrames(aWindow) |
|
151 { |
|
152 let innerWindowID = this.getInnerWindowId(aWindow); |
|
153 let ids = [innerWindowID]; |
|
154 |
|
155 if (aWindow.frames) { |
|
156 for (let i = 0; i < aWindow.frames.length; i++) { |
|
157 let frame = aWindow.frames[i]; |
|
158 ids = ids.concat(this.getInnerWindowIDsForFrames(frame)); |
|
159 } |
|
160 } |
|
161 |
|
162 return ids; |
|
163 }, |
|
164 |
|
165 |
|
166 /** |
|
167 * Gets the ID of the outer window of this DOM window. |
|
168 * |
|
169 * @param nsIDOMWindow aWindow |
|
170 * @return integer |
|
171 * Outer ID for the given aWindow. |
|
172 */ |
|
173 getOuterWindowId: function WCU_getOuterWindowId(aWindow) |
|
174 { |
|
175 return aWindow.QueryInterface(Ci.nsIInterfaceRequestor). |
|
176 getInterface(Ci.nsIDOMWindowUtils).outerWindowID; |
|
177 }, |
|
178 |
|
179 /** |
|
180 * Abbreviates the given source URL so that it can be displayed flush-right |
|
181 * without being too distracting. |
|
182 * |
|
183 * @param string aSourceURL |
|
184 * The source URL to shorten. |
|
185 * @param object [aOptions] |
|
186 * Options: |
|
187 * - onlyCropQuery: boolean that tells if the URL abbreviation function |
|
188 * should only remove the query parameters and the hash fragment from |
|
189 * the given URL. |
|
190 * @return string |
|
191 * The abbreviated form of the source URL. |
|
192 */ |
|
193 abbreviateSourceURL: |
|
194 function WCU_abbreviateSourceURL(aSourceURL, aOptions = {}) |
|
195 { |
|
196 if (!aOptions.onlyCropQuery && aSourceURL.substr(0, 5) == "data:") { |
|
197 let commaIndex = aSourceURL.indexOf(","); |
|
198 if (commaIndex > -1) { |
|
199 aSourceURL = "data:" + aSourceURL.substring(commaIndex + 1); |
|
200 } |
|
201 } |
|
202 |
|
203 // Remove any query parameters. |
|
204 let hookIndex = aSourceURL.indexOf("?"); |
|
205 if (hookIndex > -1) { |
|
206 aSourceURL = aSourceURL.substring(0, hookIndex); |
|
207 } |
|
208 |
|
209 // Remove any hash fragments. |
|
210 let hashIndex = aSourceURL.indexOf("#"); |
|
211 if (hashIndex > -1) { |
|
212 aSourceURL = aSourceURL.substring(0, hashIndex); |
|
213 } |
|
214 |
|
215 // Remove a trailing "/". |
|
216 if (aSourceURL[aSourceURL.length - 1] == "/") { |
|
217 aSourceURL = aSourceURL.replace(/\/+$/, ""); |
|
218 } |
|
219 |
|
220 // Remove all but the last path component. |
|
221 if (!aOptions.onlyCropQuery) { |
|
222 let slashIndex = aSourceURL.lastIndexOf("/"); |
|
223 if (slashIndex > -1) { |
|
224 aSourceURL = aSourceURL.substring(slashIndex + 1); |
|
225 } |
|
226 } |
|
227 |
|
228 return aSourceURL; |
|
229 }, |
|
230 |
|
231 /** |
|
232 * Tells if the given function is native or not. |
|
233 * |
|
234 * @param function aFunction |
|
235 * The function you want to check if it is native or not. |
|
236 * @return boolean |
|
237 * True if the given function is native, false otherwise. |
|
238 */ |
|
239 isNativeFunction: function WCU_isNativeFunction(aFunction) |
|
240 { |
|
241 return typeof aFunction == "function" && !("prototype" in aFunction); |
|
242 }, |
|
243 |
|
244 /** |
|
245 * Tells if the given property of the provided object is a non-native getter or |
|
246 * not. |
|
247 * |
|
248 * @param object aObject |
|
249 * The object that contains the property. |
|
250 * @param string aProp |
|
251 * The property you want to check if it is a getter or not. |
|
252 * @return boolean |
|
253 * True if the given property is a getter, false otherwise. |
|
254 */ |
|
255 isNonNativeGetter: function WCU_isNonNativeGetter(aObject, aProp) |
|
256 { |
|
257 if (typeof aObject != "object") { |
|
258 return false; |
|
259 } |
|
260 let desc = this.getPropertyDescriptor(aObject, aProp); |
|
261 return desc && desc.get && !this.isNativeFunction(desc.get); |
|
262 }, |
|
263 |
|
264 /** |
|
265 * Get the property descriptor for the given object. |
|
266 * |
|
267 * @param object aObject |
|
268 * The object that contains the property. |
|
269 * @param string aProp |
|
270 * The property you want to get the descriptor for. |
|
271 * @return object |
|
272 * Property descriptor. |
|
273 */ |
|
274 getPropertyDescriptor: function WCU_getPropertyDescriptor(aObject, aProp) |
|
275 { |
|
276 let desc = null; |
|
277 while (aObject) { |
|
278 try { |
|
279 if ((desc = Object.getOwnPropertyDescriptor(aObject, aProp))) { |
|
280 break; |
|
281 } |
|
282 } |
|
283 catch (ex if (ex.name == "NS_ERROR_XPC_BAD_CONVERT_JS" || |
|
284 ex.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO" || |
|
285 ex.name == "TypeError")) { |
|
286 // Native getters throw here. See bug 520882. |
|
287 // null throws TypeError. |
|
288 } |
|
289 try { |
|
290 aObject = Object.getPrototypeOf(aObject); |
|
291 } |
|
292 catch (ex if (ex.name == "TypeError")) { |
|
293 return desc; |
|
294 } |
|
295 } |
|
296 return desc; |
|
297 }, |
|
298 |
|
299 /** |
|
300 * Sort function for object properties. |
|
301 * |
|
302 * @param object a |
|
303 * Property descriptor. |
|
304 * @param object b |
|
305 * Property descriptor. |
|
306 * @return integer |
|
307 * -1 if a.name < b.name, |
|
308 * 1 if a.name > b.name, |
|
309 * 0 otherwise. |
|
310 */ |
|
311 propertiesSort: function WCU_propertiesSort(a, b) |
|
312 { |
|
313 // Convert the pair.name to a number for later sorting. |
|
314 let aNumber = parseFloat(a.name); |
|
315 let bNumber = parseFloat(b.name); |
|
316 |
|
317 // Sort numbers. |
|
318 if (!isNaN(aNumber) && isNaN(bNumber)) { |
|
319 return -1; |
|
320 } |
|
321 else if (isNaN(aNumber) && !isNaN(bNumber)) { |
|
322 return 1; |
|
323 } |
|
324 else if (!isNaN(aNumber) && !isNaN(bNumber)) { |
|
325 return aNumber - bNumber; |
|
326 } |
|
327 // Sort string. |
|
328 else if (a.name < b.name) { |
|
329 return -1; |
|
330 } |
|
331 else if (a.name > b.name) { |
|
332 return 1; |
|
333 } |
|
334 else { |
|
335 return 0; |
|
336 } |
|
337 }, |
|
338 |
|
339 /** |
|
340 * Create a grip for the given value. If the value is an object, |
|
341 * an object wrapper will be created. |
|
342 * |
|
343 * @param mixed aValue |
|
344 * The value you want to create a grip for, before sending it to the |
|
345 * client. |
|
346 * @param function aObjectWrapper |
|
347 * If the value is an object then the aObjectWrapper function is |
|
348 * invoked to give us an object grip. See this.getObjectGrip(). |
|
349 * @return mixed |
|
350 * The value grip. |
|
351 */ |
|
352 createValueGrip: function WCU_createValueGrip(aValue, aObjectWrapper) |
|
353 { |
|
354 switch (typeof aValue) { |
|
355 case "boolean": |
|
356 return aValue; |
|
357 case "string": |
|
358 return aObjectWrapper(aValue); |
|
359 case "number": |
|
360 if (aValue === Infinity) { |
|
361 return { type: "Infinity" }; |
|
362 } |
|
363 else if (aValue === -Infinity) { |
|
364 return { type: "-Infinity" }; |
|
365 } |
|
366 else if (Number.isNaN(aValue)) { |
|
367 return { type: "NaN" }; |
|
368 } |
|
369 else if (!aValue && 1 / aValue === -Infinity) { |
|
370 return { type: "-0" }; |
|
371 } |
|
372 return aValue; |
|
373 case "undefined": |
|
374 return { type: "undefined" }; |
|
375 case "object": |
|
376 if (aValue === null) { |
|
377 return { type: "null" }; |
|
378 } |
|
379 case "function": |
|
380 return aObjectWrapper(aValue); |
|
381 default: |
|
382 Cu.reportError("Failed to provide a grip for value of " + typeof aValue |
|
383 + ": " + aValue); |
|
384 return null; |
|
385 } |
|
386 }, |
|
387 |
|
388 /** |
|
389 * Check if the given object is an iterator or a generator. |
|
390 * |
|
391 * @param object aObject |
|
392 * The object you want to check. |
|
393 * @return boolean |
|
394 * True if the given object is an iterator or a generator, otherwise |
|
395 * false is returned. |
|
396 */ |
|
397 isIteratorOrGenerator: function WCU_isIteratorOrGenerator(aObject) |
|
398 { |
|
399 if (aObject === null) { |
|
400 return false; |
|
401 } |
|
402 |
|
403 if (typeof aObject == "object") { |
|
404 if (typeof aObject.__iterator__ == "function" || |
|
405 aObject.constructor && aObject.constructor.name == "Iterator") { |
|
406 return true; |
|
407 } |
|
408 |
|
409 try { |
|
410 let str = aObject.toString(); |
|
411 if (typeof aObject.next == "function" && |
|
412 str.indexOf("[object Generator") == 0) { |
|
413 return true; |
|
414 } |
|
415 } |
|
416 catch (ex) { |
|
417 // window.history.next throws in the typeof check above. |
|
418 return false; |
|
419 } |
|
420 } |
|
421 |
|
422 return false; |
|
423 }, |
|
424 |
|
425 /** |
|
426 * Determine if the given request mixes HTTP with HTTPS content. |
|
427 * |
|
428 * @param string aRequest |
|
429 * Location of the requested content. |
|
430 * @param string aLocation |
|
431 * Location of the current page. |
|
432 * @return boolean |
|
433 * True if the content is mixed, false if not. |
|
434 */ |
|
435 isMixedHTTPSRequest: function WCU_isMixedHTTPSRequest(aRequest, aLocation) |
|
436 { |
|
437 try { |
|
438 let requestURI = Services.io.newURI(aRequest, null, null); |
|
439 let contentURI = Services.io.newURI(aLocation, null, null); |
|
440 return (contentURI.scheme == "https" && requestURI.scheme != "https"); |
|
441 } |
|
442 catch (ex) { |
|
443 return false; |
|
444 } |
|
445 }, |
|
446 |
|
447 /** |
|
448 * Helper function to deduce the name of the provided function. |
|
449 * |
|
450 * @param funtion aFunction |
|
451 * The function whose name will be returned. |
|
452 * @return string |
|
453 * Function name. |
|
454 */ |
|
455 getFunctionName: function WCF_getFunctionName(aFunction) |
|
456 { |
|
457 let name = null; |
|
458 if (aFunction.name) { |
|
459 name = aFunction.name; |
|
460 } |
|
461 else { |
|
462 let desc; |
|
463 try { |
|
464 desc = aFunction.getOwnPropertyDescriptor("displayName"); |
|
465 } |
|
466 catch (ex) { } |
|
467 if (desc && typeof desc.value == "string") { |
|
468 name = desc.value; |
|
469 } |
|
470 } |
|
471 if (!name) { |
|
472 try { |
|
473 let str = (aFunction.toString() || aFunction.toSource()) + ""; |
|
474 name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1]; |
|
475 } |
|
476 catch (ex) { } |
|
477 } |
|
478 return name; |
|
479 }, |
|
480 |
|
481 /** |
|
482 * Get the object class name. For example, the |window| object has the Window |
|
483 * class name (based on [object Window]). |
|
484 * |
|
485 * @param object aObject |
|
486 * The object you want to get the class name for. |
|
487 * @return string |
|
488 * The object class name. |
|
489 */ |
|
490 getObjectClassName: function WCU_getObjectClassName(aObject) |
|
491 { |
|
492 if (aObject === null) { |
|
493 return "null"; |
|
494 } |
|
495 if (aObject === undefined) { |
|
496 return "undefined"; |
|
497 } |
|
498 |
|
499 let type = typeof aObject; |
|
500 if (type != "object") { |
|
501 // Grip class names should start with an uppercase letter. |
|
502 return type.charAt(0).toUpperCase() + type.substr(1); |
|
503 } |
|
504 |
|
505 let className; |
|
506 |
|
507 try { |
|
508 className = ((aObject + "").match(/^\[object (\S+)\]$/) || [])[1]; |
|
509 if (!className) { |
|
510 className = ((aObject.constructor + "").match(/^\[object (\S+)\]$/) || [])[1]; |
|
511 } |
|
512 if (!className && typeof aObject.constructor == "function") { |
|
513 className = this.getFunctionName(aObject.constructor); |
|
514 } |
|
515 } |
|
516 catch (ex) { } |
|
517 |
|
518 return className; |
|
519 }, |
|
520 |
|
521 /** |
|
522 * Check if the given value is a grip with an actor. |
|
523 * |
|
524 * @param mixed aGrip |
|
525 * Value you want to check if it is a grip with an actor. |
|
526 * @return boolean |
|
527 * True if the given value is a grip with an actor. |
|
528 */ |
|
529 isActorGrip: function WCU_isActorGrip(aGrip) |
|
530 { |
|
531 return aGrip && typeof(aGrip) == "object" && aGrip.actor; |
|
532 }, |
|
533 }; |
|
534 exports.Utils = WebConsoleUtils; |
|
535 |
|
536 ////////////////////////////////////////////////////////////////////////// |
|
537 // Localization |
|
538 ////////////////////////////////////////////////////////////////////////// |
|
539 |
|
540 WebConsoleUtils.l10n = function WCU_l10n(aBundleURI) |
|
541 { |
|
542 this._bundleUri = aBundleURI; |
|
543 }; |
|
544 |
|
545 WebConsoleUtils.l10n.prototype = { |
|
546 _stringBundle: null, |
|
547 |
|
548 get stringBundle() |
|
549 { |
|
550 if (!this._stringBundle) { |
|
551 this._stringBundle = Services.strings.createBundle(this._bundleUri); |
|
552 } |
|
553 return this._stringBundle; |
|
554 }, |
|
555 |
|
556 /** |
|
557 * Generates a formatted timestamp string for displaying in console messages. |
|
558 * |
|
559 * @param integer [aMilliseconds] |
|
560 * Optional, allows you to specify the timestamp in milliseconds since |
|
561 * the UNIX epoch. |
|
562 * @return string |
|
563 * The timestamp formatted for display. |
|
564 */ |
|
565 timestampString: function WCU_l10n_timestampString(aMilliseconds) |
|
566 { |
|
567 let d = new Date(aMilliseconds ? aMilliseconds : null); |
|
568 let hours = d.getHours(), minutes = d.getMinutes(); |
|
569 let seconds = d.getSeconds(), milliseconds = d.getMilliseconds(); |
|
570 let parameters = [hours, minutes, seconds, milliseconds]; |
|
571 return this.getFormatStr("timestampFormat", parameters); |
|
572 }, |
|
573 |
|
574 /** |
|
575 * Retrieve a localized string. |
|
576 * |
|
577 * @param string aName |
|
578 * The string name you want from the Web Console string bundle. |
|
579 * @return string |
|
580 * The localized string. |
|
581 */ |
|
582 getStr: function WCU_l10n_getStr(aName) |
|
583 { |
|
584 let result; |
|
585 try { |
|
586 result = this.stringBundle.GetStringFromName(aName); |
|
587 } |
|
588 catch (ex) { |
|
589 Cu.reportError("Failed to get string: " + aName); |
|
590 throw ex; |
|
591 } |
|
592 return result; |
|
593 }, |
|
594 |
|
595 /** |
|
596 * Retrieve a localized string formatted with values coming from the given |
|
597 * array. |
|
598 * |
|
599 * @param string aName |
|
600 * The string name you want from the Web Console string bundle. |
|
601 * @param array aArray |
|
602 * The array of values you want in the formatted string. |
|
603 * @return string |
|
604 * The formatted local string. |
|
605 */ |
|
606 getFormatStr: function WCU_l10n_getFormatStr(aName, aArray) |
|
607 { |
|
608 let result; |
|
609 try { |
|
610 result = this.stringBundle.formatStringFromName(aName, aArray, aArray.length); |
|
611 } |
|
612 catch (ex) { |
|
613 Cu.reportError("Failed to format string: " + aName); |
|
614 throw ex; |
|
615 } |
|
616 return result; |
|
617 }, |
|
618 }; |
|
619 |
|
620 |
|
621 ////////////////////////////////////////////////////////////////////////// |
|
622 // JS Completer |
|
623 ////////////////////////////////////////////////////////////////////////// |
|
624 |
|
625 (function _JSPP(WCU) { |
|
626 const STATE_NORMAL = 0; |
|
627 const STATE_QUOTE = 2; |
|
628 const STATE_DQUOTE = 3; |
|
629 |
|
630 const OPEN_BODY = "{[(".split(""); |
|
631 const CLOSE_BODY = "}])".split(""); |
|
632 const OPEN_CLOSE_BODY = { |
|
633 "{": "}", |
|
634 "[": "]", |
|
635 "(": ")", |
|
636 }; |
|
637 |
|
638 const MAX_COMPLETIONS = 1500; |
|
639 |
|
640 /** |
|
641 * Analyses a given string to find the last statement that is interesting for |
|
642 * later completion. |
|
643 * |
|
644 * @param string aStr |
|
645 * A string to analyse. |
|
646 * |
|
647 * @returns object |
|
648 * If there was an error in the string detected, then a object like |
|
649 * |
|
650 * { err: "ErrorMesssage" } |
|
651 * |
|
652 * is returned, otherwise a object like |
|
653 * |
|
654 * { |
|
655 * state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE, |
|
656 * startPos: index of where the last statement begins |
|
657 * } |
|
658 */ |
|
659 function findCompletionBeginning(aStr) |
|
660 { |
|
661 let bodyStack = []; |
|
662 |
|
663 let state = STATE_NORMAL; |
|
664 let start = 0; |
|
665 let c; |
|
666 for (let i = 0; i < aStr.length; i++) { |
|
667 c = aStr[i]; |
|
668 |
|
669 switch (state) { |
|
670 // Normal JS state. |
|
671 case STATE_NORMAL: |
|
672 if (c == '"') { |
|
673 state = STATE_DQUOTE; |
|
674 } |
|
675 else if (c == "'") { |
|
676 state = STATE_QUOTE; |
|
677 } |
|
678 else if (c == ";") { |
|
679 start = i + 1; |
|
680 } |
|
681 else if (c == " ") { |
|
682 start = i + 1; |
|
683 } |
|
684 else if (OPEN_BODY.indexOf(c) != -1) { |
|
685 bodyStack.push({ |
|
686 token: c, |
|
687 start: start |
|
688 }); |
|
689 start = i + 1; |
|
690 } |
|
691 else if (CLOSE_BODY.indexOf(c) != -1) { |
|
692 var last = bodyStack.pop(); |
|
693 if (!last || OPEN_CLOSE_BODY[last.token] != c) { |
|
694 return { |
|
695 err: "syntax error" |
|
696 }; |
|
697 } |
|
698 if (c == "}") { |
|
699 start = i + 1; |
|
700 } |
|
701 else { |
|
702 start = last.start; |
|
703 } |
|
704 } |
|
705 break; |
|
706 |
|
707 // Double quote state > " < |
|
708 case STATE_DQUOTE: |
|
709 if (c == "\\") { |
|
710 i++; |
|
711 } |
|
712 else if (c == "\n") { |
|
713 return { |
|
714 err: "unterminated string literal" |
|
715 }; |
|
716 } |
|
717 else if (c == '"') { |
|
718 state = STATE_NORMAL; |
|
719 } |
|
720 break; |
|
721 |
|
722 // Single quote state > ' < |
|
723 case STATE_QUOTE: |
|
724 if (c == "\\") { |
|
725 i++; |
|
726 } |
|
727 else if (c == "\n") { |
|
728 return { |
|
729 err: "unterminated string literal" |
|
730 }; |
|
731 } |
|
732 else if (c == "'") { |
|
733 state = STATE_NORMAL; |
|
734 } |
|
735 break; |
|
736 } |
|
737 } |
|
738 |
|
739 return { |
|
740 state: state, |
|
741 startPos: start |
|
742 }; |
|
743 } |
|
744 |
|
745 /** |
|
746 * Provides a list of properties, that are possible matches based on the passed |
|
747 * Debugger.Environment/Debugger.Object and inputValue. |
|
748 * |
|
749 * @param object aDbgObject |
|
750 * When the debugger is not paused this Debugger.Object wraps the scope for autocompletion. |
|
751 * It is null if the debugger is paused. |
|
752 * @param object anEnvironment |
|
753 * When the debugger is paused this Debugger.Environment is the scope for autocompletion. |
|
754 * It is null if the debugger is not paused. |
|
755 * @param string aInputValue |
|
756 * Value that should be completed. |
|
757 * @param number [aCursor=aInputValue.length] |
|
758 * Optional offset in the input where the cursor is located. If this is |
|
759 * omitted then the cursor is assumed to be at the end of the input |
|
760 * value. |
|
761 * @returns null or object |
|
762 * If no completion valued could be computed, null is returned, |
|
763 * otherwise a object with the following form is returned: |
|
764 * { |
|
765 * matches: [ string, string, string ], |
|
766 * matchProp: Last part of the inputValue that was used to find |
|
767 * the matches-strings. |
|
768 * } |
|
769 */ |
|
770 function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor) |
|
771 { |
|
772 if (aCursor === undefined) { |
|
773 aCursor = aInputValue.length; |
|
774 } |
|
775 |
|
776 let inputValue = aInputValue.substring(0, aCursor); |
|
777 |
|
778 // Analyse the inputValue and find the beginning of the last part that |
|
779 // should be completed. |
|
780 let beginning = findCompletionBeginning(inputValue); |
|
781 |
|
782 // There was an error analysing the string. |
|
783 if (beginning.err) { |
|
784 return null; |
|
785 } |
|
786 |
|
787 // If the current state is not STATE_NORMAL, then we are inside of an string |
|
788 // which means that no completion is possible. |
|
789 if (beginning.state != STATE_NORMAL) { |
|
790 return null; |
|
791 } |
|
792 |
|
793 let completionPart = inputValue.substring(beginning.startPos); |
|
794 |
|
795 // Don't complete on just an empty string. |
|
796 if (completionPart.trim() == "") { |
|
797 return null; |
|
798 } |
|
799 |
|
800 let lastDot = completionPart.lastIndexOf("."); |
|
801 if (lastDot > 0 && |
|
802 (completionPart[0] == "'" || completionPart[0] == '"') && |
|
803 completionPart[lastDot - 1] == completionPart[0]) { |
|
804 // We are completing a string literal. |
|
805 let matchProp = completionPart.slice(lastDot + 1); |
|
806 return getMatchedProps(String.prototype, matchProp); |
|
807 } |
|
808 |
|
809 // We are completing a variable / a property lookup. |
|
810 let properties = completionPart.split("."); |
|
811 let matchProp = properties.pop().trimLeft(); |
|
812 let obj = aDbgObject; |
|
813 |
|
814 // The first property must be found in the environment if the debugger is |
|
815 // paused. |
|
816 if (anEnvironment) { |
|
817 if (properties.length == 0) { |
|
818 return getMatchedPropsInEnvironment(anEnvironment, matchProp); |
|
819 } |
|
820 obj = getVariableInEnvironment(anEnvironment, properties.shift()); |
|
821 } |
|
822 |
|
823 if (!isObjectUsable(obj)) { |
|
824 return null; |
|
825 } |
|
826 |
|
827 // We get the rest of the properties recursively starting from the Debugger.Object |
|
828 // that wraps the first property |
|
829 for (let prop of properties) { |
|
830 prop = prop.trim(); |
|
831 if (!prop) { |
|
832 return null; |
|
833 } |
|
834 |
|
835 if (/\[\d+\]$/.test(prop))Â { |
|
836 // The property to autocomplete is a member of array. For example |
|
837 // list[i][j]..[n]. Traverse the array to get the actual element. |
|
838 obj = getArrayMemberProperty(obj, prop); |
|
839 } |
|
840 else { |
|
841 obj = DevToolsUtils.getProperty(obj, prop); |
|
842 } |
|
843 |
|
844 if (!isObjectUsable(obj)) { |
|
845 return null; |
|
846 } |
|
847 } |
|
848 |
|
849 // If the final property is a primitive |
|
850 if (typeof obj != "object") { |
|
851 return getMatchedProps(obj, matchProp); |
|
852 } |
|
853 |
|
854 return getMatchedPropsInDbgObject(obj, matchProp); |
|
855 } |
|
856 |
|
857 /** |
|
858 * Get the array member of aObj for the given aProp. For example, given |
|
859 * aProp='list[0][1]' the element at [0][1] of aObj.list is returned. |
|
860 * |
|
861 * @param object aObj |
|
862 * The object to operate on. |
|
863 * @param string aProp |
|
864 * The property to return. |
|
865 * @return null or Object |
|
866 * Returns null if the property couldn't be located. Otherwise the array |
|
867 * member identified by aProp. |
|
868 */ |
|
869 function getArrayMemberProperty(aObj, aProp) |
|
870 { |
|
871 // First get the array. |
|
872 let obj = aObj; |
|
873 let propWithoutIndices = aProp.substr(0, aProp.indexOf("[")); |
|
874 obj = DevToolsUtils.getProperty(obj, propWithoutIndices); |
|
875 if (!isObjectUsable(obj)) { |
|
876 return null; |
|
877 } |
|
878 |
|
879 // Then traverse the list of indices to get the actual element. |
|
880 let result; |
|
881 let arrayIndicesRegex = /\[[^\]]*\]/g; |
|
882 while ((result = arrayIndicesRegex.exec(aProp)) !== null) { |
|
883 let indexWithBrackets = result[0]; |
|
884 let indexAsText = indexWithBrackets.substr(1, indexWithBrackets.length - 2); |
|
885 let index = parseInt(indexAsText); |
|
886 |
|
887 if (isNaN(index)) { |
|
888 return null; |
|
889 } |
|
890 |
|
891 obj = DevToolsUtils.getProperty(obj, index); |
|
892 |
|
893 if (!isObjectUsable(obj)) { |
|
894 return null; |
|
895 } |
|
896 } |
|
897 |
|
898 return obj; |
|
899 } |
|
900 |
|
901 /** |
|
902 * Check if the given Debugger.Object can be used for autocomplete. |
|
903 * |
|
904 * @param Debugger.Object aObject |
|
905 * The Debugger.Object to check. |
|
906 * @return boolean |
|
907 * True if further inspection into the object is possible, or false |
|
908 * otherwise. |
|
909 */ |
|
910 function isObjectUsable(aObject) |
|
911 { |
|
912 if (aObject == null) { |
|
913 return false; |
|
914 } |
|
915 |
|
916 if (typeof aObject == "object" && aObject.class == "DeadObject") { |
|
917 return false; |
|
918 } |
|
919 |
|
920 return true; |
|
921 } |
|
922 |
|
923 /** |
|
924 * @see getExactMatch_impl() |
|
925 */ |
|
926 function getVariableInEnvironment(anEnvironment, aName) |
|
927 { |
|
928 return getExactMatch_impl(anEnvironment, aName, DebuggerEnvironmentSupport); |
|
929 } |
|
930 |
|
931 /** |
|
932 * @see getMatchedProps_impl() |
|
933 */ |
|
934 function getMatchedPropsInEnvironment(anEnvironment, aMatch) |
|
935 { |
|
936 return getMatchedProps_impl(anEnvironment, aMatch, DebuggerEnvironmentSupport); |
|
937 } |
|
938 |
|
939 /** |
|
940 * @see getMatchedProps_impl() |
|
941 */ |
|
942 function getMatchedPropsInDbgObject(aDbgObject, aMatch) |
|
943 { |
|
944 return getMatchedProps_impl(aDbgObject, aMatch, DebuggerObjectSupport); |
|
945 } |
|
946 |
|
947 /** |
|
948 * @see getMatchedProps_impl() |
|
949 */ |
|
950 function getMatchedProps(aObj, aMatch) |
|
951 { |
|
952 if (typeof aObj != "object") { |
|
953 aObj = aObj.constructor.prototype; |
|
954 } |
|
955 return getMatchedProps_impl(aObj, aMatch, JSObjectSupport); |
|
956 } |
|
957 |
|
958 /** |
|
959 * Get all properties in the given object (and its parent prototype chain) that |
|
960 * match a given prefix. |
|
961 * |
|
962 * @param mixed aObj |
|
963 * Object whose properties we want to filter. |
|
964 * @param string aMatch |
|
965 * Filter for properties that match this string. |
|
966 * @return object |
|
967 * Object that contains the matchProp and the list of names. |
|
968 */ |
|
969 function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties}) |
|
970 { |
|
971 let matches = new Set(); |
|
972 |
|
973 // We need to go up the prototype chain. |
|
974 let iter = chainIterator(aObj); |
|
975 for (let obj of iter) { |
|
976 let props = getProperties(obj); |
|
977 for (let prop of props) { |
|
978 if (prop.indexOf(aMatch) != 0) { |
|
979 continue; |
|
980 } |
|
981 |
|
982 // If it is an array index, we can't take it. |
|
983 // This uses a trick: converting a string to a number yields NaN if |
|
984 // the operation failed, and NaN is not equal to itself. |
|
985 if (+prop != +prop) { |
|
986 matches.add(prop); |
|
987 } |
|
988 |
|
989 if (matches.size > MAX_COMPLETIONS) { |
|
990 break; |
|
991 } |
|
992 } |
|
993 |
|
994 if (matches.size > MAX_COMPLETIONS) { |
|
995 break; |
|
996 } |
|
997 } |
|
998 |
|
999 return { |
|
1000 matchProp: aMatch, |
|
1001 matches: [...matches], |
|
1002 }; |
|
1003 } |
|
1004 |
|
1005 /** |
|
1006 * Returns a property value based on its name from the given object, by |
|
1007 * recursively checking the object's prototype. |
|
1008 * |
|
1009 * @param object aObj |
|
1010 * An object to look the property into. |
|
1011 * @param string aName |
|
1012 * The property that is looked up. |
|
1013 * @returns object|undefined |
|
1014 * A Debugger.Object if the property exists in the object's prototype |
|
1015 * chain, undefined otherwise. |
|
1016 */ |
|
1017 function getExactMatch_impl(aObj, aName, {chainIterator, getProperty}) |
|
1018 { |
|
1019 // We need to go up the prototype chain. |
|
1020 let iter = chainIterator(aObj); |
|
1021 for (let obj of iter) { |
|
1022 let prop = getProperty(obj, aName, aObj); |
|
1023 if (prop) { |
|
1024 return prop.value; |
|
1025 } |
|
1026 } |
|
1027 return undefined; |
|
1028 } |
|
1029 |
|
1030 |
|
1031 let JSObjectSupport = { |
|
1032 chainIterator: function(aObj) |
|
1033 { |
|
1034 while (aObj) { |
|
1035 yield aObj; |
|
1036 aObj = Object.getPrototypeOf(aObj); |
|
1037 } |
|
1038 }, |
|
1039 |
|
1040 getProperties: function(aObj) |
|
1041 { |
|
1042 return Object.getOwnPropertyNames(aObj); |
|
1043 }, |
|
1044 |
|
1045 getProperty: function() |
|
1046 { |
|
1047 // getProperty is unsafe with raw JS objects. |
|
1048 throw "Unimplemented!"; |
|
1049 }, |
|
1050 }; |
|
1051 |
|
1052 let DebuggerObjectSupport = { |
|
1053 chainIterator: function(aObj) |
|
1054 { |
|
1055 while (aObj) { |
|
1056 yield aObj; |
|
1057 aObj = aObj.proto; |
|
1058 } |
|
1059 }, |
|
1060 |
|
1061 getProperties: function(aObj) |
|
1062 { |
|
1063 return aObj.getOwnPropertyNames(); |
|
1064 }, |
|
1065 |
|
1066 getProperty: function(aObj, aName, aRootObj) |
|
1067 { |
|
1068 // This is left unimplemented in favor to DevToolsUtils.getProperty(). |
|
1069 throw "Unimplemented!"; |
|
1070 }, |
|
1071 }; |
|
1072 |
|
1073 let DebuggerEnvironmentSupport = { |
|
1074 chainIterator: function(aObj) |
|
1075 { |
|
1076 while (aObj) { |
|
1077 yield aObj; |
|
1078 aObj = aObj.parent; |
|
1079 } |
|
1080 }, |
|
1081 |
|
1082 getProperties: function(aObj) |
|
1083 { |
|
1084 return aObj.names(); |
|
1085 }, |
|
1086 |
|
1087 getProperty: function(aObj, aName) |
|
1088 { |
|
1089 // TODO: we should use getVariableDescriptor() here - bug 725815. |
|
1090 let result = aObj.getVariable(aName); |
|
1091 // FIXME: Need actual UI, bug 941287. |
|
1092 if (result.optimizedOut || result.missingArguments) { |
|
1093 return null; |
|
1094 } |
|
1095 return result === undefined ? null : { value: result }; |
|
1096 }, |
|
1097 }; |
|
1098 |
|
1099 |
|
1100 exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider); |
|
1101 })(WebConsoleUtils); |
|
1102 |
|
1103 /////////////////////////////////////////////////////////////////////////////// |
|
1104 // The page errors listener |
|
1105 /////////////////////////////////////////////////////////////////////////////// |
|
1106 |
|
1107 /** |
|
1108 * The nsIConsoleService listener. This is used to send all of the console |
|
1109 * messages (JavaScript, CSS and more) to the remote Web Console instance. |
|
1110 * |
|
1111 * @constructor |
|
1112 * @param nsIDOMWindow [aWindow] |
|
1113 * Optional - the window object for which we are created. This is used |
|
1114 * for filtering out messages that belong to other windows. |
|
1115 * @param object aListener |
|
1116 * The listener object must have one method: |
|
1117 * - onConsoleServiceMessage(). This method is invoked with one argument, |
|
1118 * the nsIConsoleMessage, whenever a relevant message is received. |
|
1119 */ |
|
1120 function ConsoleServiceListener(aWindow, aListener) |
|
1121 { |
|
1122 this.window = aWindow; |
|
1123 this.listener = aListener; |
|
1124 if (this.window) { |
|
1125 this.layoutHelpers = new LayoutHelpers(this.window); |
|
1126 } |
|
1127 } |
|
1128 exports.ConsoleServiceListener = ConsoleServiceListener; |
|
1129 |
|
1130 ConsoleServiceListener.prototype = |
|
1131 { |
|
1132 QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]), |
|
1133 |
|
1134 /** |
|
1135 * The content window for which we listen to page errors. |
|
1136 * @type nsIDOMWindow |
|
1137 */ |
|
1138 window: null, |
|
1139 |
|
1140 /** |
|
1141 * The listener object which is notified of messages from the console service. |
|
1142 * @type object |
|
1143 */ |
|
1144 listener: null, |
|
1145 |
|
1146 /** |
|
1147 * Initialize the nsIConsoleService listener. |
|
1148 */ |
|
1149 init: function CSL_init() |
|
1150 { |
|
1151 Services.console.registerListener(this); |
|
1152 }, |
|
1153 |
|
1154 /** |
|
1155 * The nsIConsoleService observer. This method takes all the script error |
|
1156 * messages belonging to the current window and sends them to the remote Web |
|
1157 * Console instance. |
|
1158 * |
|
1159 * @param nsIConsoleMessage aMessage |
|
1160 * The message object coming from the nsIConsoleService. |
|
1161 */ |
|
1162 observe: function CSL_observe(aMessage) |
|
1163 { |
|
1164 if (!this.listener) { |
|
1165 return; |
|
1166 } |
|
1167 |
|
1168 if (this.window) { |
|
1169 if (!(aMessage instanceof Ci.nsIScriptError) || |
|
1170 !aMessage.outerWindowID || |
|
1171 !this.isCategoryAllowed(aMessage.category)) { |
|
1172 return; |
|
1173 } |
|
1174 |
|
1175 let errorWindow = Services.wm.getOuterWindowWithId(aMessage.outerWindowID); |
|
1176 if (!errorWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(errorWindow)) { |
|
1177 return; |
|
1178 } |
|
1179 } |
|
1180 |
|
1181 this.listener.onConsoleServiceMessage(aMessage); |
|
1182 }, |
|
1183 |
|
1184 /** |
|
1185 * Check if the given message category is allowed to be tracked or not. |
|
1186 * We ignore chrome-originating errors as we only care about content. |
|
1187 * |
|
1188 * @param string aCategory |
|
1189 * The message category you want to check. |
|
1190 * @return boolean |
|
1191 * True if the category is allowed to be logged, false otherwise. |
|
1192 */ |
|
1193 isCategoryAllowed: function CSL_isCategoryAllowed(aCategory) |
|
1194 { |
|
1195 if (!aCategory) { |
|
1196 return false; |
|
1197 } |
|
1198 |
|
1199 switch (aCategory) { |
|
1200 case "XPConnect JavaScript": |
|
1201 case "component javascript": |
|
1202 case "chrome javascript": |
|
1203 case "chrome registration": |
|
1204 case "XBL": |
|
1205 case "XBL Prototype Handler": |
|
1206 case "XBL Content Sink": |
|
1207 case "xbl javascript": |
|
1208 return false; |
|
1209 } |
|
1210 |
|
1211 return true; |
|
1212 }, |
|
1213 |
|
1214 /** |
|
1215 * Get the cached page errors for the current inner window and its (i)frames. |
|
1216 * |
|
1217 * @param boolean [aIncludePrivate=false] |
|
1218 * Tells if you want to also retrieve messages coming from private |
|
1219 * windows. Defaults to false. |
|
1220 * @return array |
|
1221 * The array of cached messages. Each element is an nsIScriptError or |
|
1222 * an nsIConsoleMessage |
|
1223 */ |
|
1224 getCachedMessages: function CSL_getCachedMessages(aIncludePrivate = false) |
|
1225 { |
|
1226 let errors = Services.console.getMessageArray() || []; |
|
1227 |
|
1228 // if !this.window, we're in a browser console. Still need to filter |
|
1229 // private messages. |
|
1230 if (!this.window) { |
|
1231 return errors.filter((aError) => { |
|
1232 if (aError instanceof Ci.nsIScriptError) { |
|
1233 if (!aIncludePrivate && aError.isFromPrivateWindow) { |
|
1234 return false; |
|
1235 } |
|
1236 } |
|
1237 |
|
1238 return true; |
|
1239 }); |
|
1240 } |
|
1241 |
|
1242 let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window); |
|
1243 |
|
1244 return errors.filter((aError) => { |
|
1245 if (aError instanceof Ci.nsIScriptError) { |
|
1246 if (!aIncludePrivate && aError.isFromPrivateWindow) { |
|
1247 return false; |
|
1248 } |
|
1249 if (ids && |
|
1250 (ids.indexOf(aError.innerWindowID) == -1 || |
|
1251 !this.isCategoryAllowed(aError.category))) { |
|
1252 return false; |
|
1253 } |
|
1254 } |
|
1255 else if (ids && ids[0]) { |
|
1256 // If this is not an nsIScriptError and we need to do window-based |
|
1257 // filtering we skip this message. |
|
1258 return false; |
|
1259 } |
|
1260 |
|
1261 return true; |
|
1262 }); |
|
1263 }, |
|
1264 |
|
1265 /** |
|
1266 * Remove the nsIConsoleService listener. |
|
1267 */ |
|
1268 destroy: function CSL_destroy() |
|
1269 { |
|
1270 Services.console.unregisterListener(this); |
|
1271 this.listener = this.window = null; |
|
1272 }, |
|
1273 }; |
|
1274 |
|
1275 |
|
1276 /////////////////////////////////////////////////////////////////////////////// |
|
1277 // The window.console API observer |
|
1278 /////////////////////////////////////////////////////////////////////////////// |
|
1279 |
|
1280 /** |
|
1281 * The window.console API observer. This allows the window.console API messages |
|
1282 * to be sent to the remote Web Console instance. |
|
1283 * |
|
1284 * @constructor |
|
1285 * @param nsIDOMWindow aWindow |
|
1286 * Optional - the window object for which we are created. This is used |
|
1287 * for filtering out messages that belong to other windows. |
|
1288 * @param object aOwner |
|
1289 * The owner object must have the following methods: |
|
1290 * - onConsoleAPICall(). This method is invoked with one argument, the |
|
1291 * Console API message that comes from the observer service, whenever |
|
1292 * a relevant console API call is received. |
|
1293 */ |
|
1294 function ConsoleAPIListener(aWindow, aOwner) |
|
1295 { |
|
1296 this.window = aWindow; |
|
1297 this.owner = aOwner; |
|
1298 if (this.window) { |
|
1299 this.layoutHelpers = new LayoutHelpers(this.window); |
|
1300 } |
|
1301 } |
|
1302 exports.ConsoleAPIListener = ConsoleAPIListener; |
|
1303 |
|
1304 ConsoleAPIListener.prototype = |
|
1305 { |
|
1306 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), |
|
1307 |
|
1308 /** |
|
1309 * The content window for which we listen to window.console API calls. |
|
1310 * @type nsIDOMWindow |
|
1311 */ |
|
1312 window: null, |
|
1313 |
|
1314 /** |
|
1315 * The owner object which is notified of window.console API calls. It must |
|
1316 * have a onConsoleAPICall method which is invoked with one argument: the |
|
1317 * console API call object that comes from the observer service. |
|
1318 * |
|
1319 * @type object |
|
1320 * @see WebConsoleActor |
|
1321 */ |
|
1322 owner: null, |
|
1323 |
|
1324 /** |
|
1325 * Initialize the window.console API observer. |
|
1326 */ |
|
1327 init: function CAL_init() |
|
1328 { |
|
1329 // Note that the observer is process-wide. We will filter the messages as |
|
1330 // needed, see CAL_observe(). |
|
1331 Services.obs.addObserver(this, "console-api-log-event", false); |
|
1332 }, |
|
1333 |
|
1334 /** |
|
1335 * The console API message observer. When messages are received from the |
|
1336 * observer service we forward them to the remote Web Console instance. |
|
1337 * |
|
1338 * @param object aMessage |
|
1339 * The message object receives from the observer service. |
|
1340 * @param string aTopic |
|
1341 * The message topic received from the observer service. |
|
1342 */ |
|
1343 observe: function CAL_observe(aMessage, aTopic) |
|
1344 { |
|
1345 if (!this.owner) { |
|
1346 return; |
|
1347 } |
|
1348 |
|
1349 let apiMessage = aMessage.wrappedJSObject; |
|
1350 if (this.window) { |
|
1351 let msgWindow = Services.wm.getCurrentInnerWindowWithId(apiMessage.innerID); |
|
1352 if (!msgWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(msgWindow)) { |
|
1353 // Not the same window! |
|
1354 return; |
|
1355 } |
|
1356 } |
|
1357 |
|
1358 this.owner.onConsoleAPICall(apiMessage); |
|
1359 }, |
|
1360 |
|
1361 /** |
|
1362 * Get the cached messages for the current inner window and its (i)frames. |
|
1363 * |
|
1364 * @param boolean [aIncludePrivate=false] |
|
1365 * Tells if you want to also retrieve messages coming from private |
|
1366 * windows. Defaults to false. |
|
1367 * @return array |
|
1368 * The array of cached messages. |
|
1369 */ |
|
1370 getCachedMessages: function CAL_getCachedMessages(aIncludePrivate = false) |
|
1371 { |
|
1372 let messages = []; |
|
1373 let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"] |
|
1374 .getService(Ci.nsIConsoleAPIStorage); |
|
1375 |
|
1376 // if !this.window, we're in a browser console. Retrieve all events |
|
1377 // for filtering based on privacy. |
|
1378 if (!this.window) { |
|
1379 messages = ConsoleAPIStorage.getEvents(); |
|
1380 } else { |
|
1381 let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window); |
|
1382 ids.forEach((id) => { |
|
1383 messages = messages.concat(ConsoleAPIStorage.getEvents(id)); |
|
1384 }); |
|
1385 } |
|
1386 |
|
1387 if (aIncludePrivate) { |
|
1388 return messages; |
|
1389 } |
|
1390 |
|
1391 return messages.filter((m) => !m.private); |
|
1392 }, |
|
1393 |
|
1394 /** |
|
1395 * Destroy the console API listener. |
|
1396 */ |
|
1397 destroy: function CAL_destroy() |
|
1398 { |
|
1399 Services.obs.removeObserver(this, "console-api-log-event"); |
|
1400 this.window = this.owner = null; |
|
1401 }, |
|
1402 }; |
|
1403 |
|
1404 |
|
1405 |
|
1406 /** |
|
1407 * JSTerm helper functions. |
|
1408 * |
|
1409 * Defines a set of functions ("helper functions") that are available from the |
|
1410 * Web Console but not from the web page. |
|
1411 * |
|
1412 * A list of helper functions used by Firebug can be found here: |
|
1413 * http://getfirebug.com/wiki/index.php/Command_Line_API |
|
1414 * |
|
1415 * @param object aOwner |
|
1416 * The owning object. |
|
1417 */ |
|
1418 function JSTermHelpers(aOwner) |
|
1419 { |
|
1420 /** |
|
1421 * Find a node by ID. |
|
1422 * |
|
1423 * @param string aId |
|
1424 * The ID of the element you want. |
|
1425 * @return nsIDOMNode or null |
|
1426 * The result of calling document.querySelector(aSelector). |
|
1427 */ |
|
1428 aOwner.sandbox.$ = function JSTH_$(aSelector) |
|
1429 { |
|
1430 return aOwner.window.document.querySelector(aSelector); |
|
1431 }; |
|
1432 |
|
1433 /** |
|
1434 * Find the nodes matching a CSS selector. |
|
1435 * |
|
1436 * @param string aSelector |
|
1437 * A string that is passed to window.document.querySelectorAll. |
|
1438 * @return nsIDOMNodeList |
|
1439 * Returns the result of document.querySelectorAll(aSelector). |
|
1440 */ |
|
1441 aOwner.sandbox.$$ = function JSTH_$$(aSelector) |
|
1442 { |
|
1443 return aOwner.window.document.querySelectorAll(aSelector); |
|
1444 }; |
|
1445 |
|
1446 /** |
|
1447 * Runs an xPath query and returns all matched nodes. |
|
1448 * |
|
1449 * @param string aXPath |
|
1450 * xPath search query to execute. |
|
1451 * @param [optional] nsIDOMNode aContext |
|
1452 * Context to run the xPath query on. Uses window.document if not set. |
|
1453 * @return array of nsIDOMNode |
|
1454 */ |
|
1455 aOwner.sandbox.$x = function JSTH_$x(aXPath, aContext) |
|
1456 { |
|
1457 let nodes = new aOwner.window.wrappedJSObject.Array(); |
|
1458 let doc = aOwner.window.document; |
|
1459 aContext = aContext || doc; |
|
1460 |
|
1461 let results = doc.evaluate(aXPath, aContext, null, |
|
1462 Ci.nsIDOMXPathResult.ANY_TYPE, null); |
|
1463 let node; |
|
1464 while ((node = results.iterateNext())) { |
|
1465 nodes.push(node); |
|
1466 } |
|
1467 |
|
1468 return nodes; |
|
1469 }; |
|
1470 |
|
1471 /** |
|
1472 * Returns the currently selected object in the highlighter. |
|
1473 * |
|
1474 * TODO: this implementation crosses the client/server boundaries! This is not |
|
1475 * usable within a remote browser. To implement this feature correctly we need |
|
1476 * support for remote inspection capabilities within the Inspector as well. |
|
1477 * See bug 787975. |
|
1478 * |
|
1479 * @return nsIDOMElement|null |
|
1480 * The DOM element currently selected in the highlighter. |
|
1481 */ |
|
1482 Object.defineProperty(aOwner.sandbox, "$0", { |
|
1483 get: function() { |
|
1484 let window = aOwner.chromeWindow(); |
|
1485 if (!window) { |
|
1486 return null; |
|
1487 } |
|
1488 |
|
1489 let target = null; |
|
1490 try { |
|
1491 target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab); |
|
1492 } |
|
1493 catch (ex) { |
|
1494 // If we report this exception the user will get it in the Browser |
|
1495 // Console every time when she evaluates any string. |
|
1496 } |
|
1497 |
|
1498 if (!target) { |
|
1499 return null; |
|
1500 } |
|
1501 |
|
1502 let toolbox = gDevTools.getToolbox(target); |
|
1503 let node = toolbox && toolbox.selection ? toolbox.selection.node : null; |
|
1504 |
|
1505 return node ? aOwner.makeDebuggeeValue(node) : null; |
|
1506 }, |
|
1507 enumerable: true, |
|
1508 configurable: false |
|
1509 }); |
|
1510 |
|
1511 /** |
|
1512 * Clears the output of the JSTerm. |
|
1513 */ |
|
1514 aOwner.sandbox.clear = function JSTH_clear() |
|
1515 { |
|
1516 aOwner.helperResult = { |
|
1517 type: "clearOutput", |
|
1518 }; |
|
1519 }; |
|
1520 |
|
1521 /** |
|
1522 * Returns the result of Object.keys(aObject). |
|
1523 * |
|
1524 * @param object aObject |
|
1525 * Object to return the property names from. |
|
1526 * @return array of strings |
|
1527 */ |
|
1528 aOwner.sandbox.keys = function JSTH_keys(aObject) |
|
1529 { |
|
1530 return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject)); |
|
1531 }; |
|
1532 |
|
1533 /** |
|
1534 * Returns the values of all properties on aObject. |
|
1535 * |
|
1536 * @param object aObject |
|
1537 * Object to display the values from. |
|
1538 * @return array of string |
|
1539 */ |
|
1540 aOwner.sandbox.values = function JSTH_values(aObject) |
|
1541 { |
|
1542 let arrValues = new aOwner.window.wrappedJSObject.Array(); |
|
1543 let obj = WebConsoleUtils.unwrap(aObject); |
|
1544 |
|
1545 for (let prop in obj) { |
|
1546 arrValues.push(obj[prop]); |
|
1547 } |
|
1548 |
|
1549 return arrValues; |
|
1550 }; |
|
1551 |
|
1552 /** |
|
1553 * Opens a help window in MDN. |
|
1554 */ |
|
1555 aOwner.sandbox.help = function JSTH_help() |
|
1556 { |
|
1557 aOwner.helperResult = { type: "help" }; |
|
1558 }; |
|
1559 |
|
1560 /** |
|
1561 * Change the JS evaluation scope. |
|
1562 * |
|
1563 * @param DOMElement|string|window aWindow |
|
1564 * The window object to use for eval scope. This can be a string that |
|
1565 * is used to perform document.querySelector(), to find the iframe that |
|
1566 * you want to cd() to. A DOMElement can be given as well, the |
|
1567 * .contentWindow property is used. Lastly, you can directly pass |
|
1568 * a window object. If you call cd() with no arguments, the current |
|
1569 * eval scope is cleared back to its default (the top window). |
|
1570 */ |
|
1571 aOwner.sandbox.cd = function JSTH_cd(aWindow) |
|
1572 { |
|
1573 if (!aWindow) { |
|
1574 aOwner.consoleActor.evalWindow = null; |
|
1575 aOwner.helperResult = { type: "cd" }; |
|
1576 return; |
|
1577 } |
|
1578 |
|
1579 if (typeof aWindow == "string") { |
|
1580 aWindow = aOwner.window.document.querySelector(aWindow); |
|
1581 } |
|
1582 if (aWindow instanceof Ci.nsIDOMElement && aWindow.contentWindow) { |
|
1583 aWindow = aWindow.contentWindow; |
|
1584 } |
|
1585 if (!(aWindow instanceof Ci.nsIDOMWindow)) { |
|
1586 aOwner.helperResult = { type: "error", message: "cdFunctionInvalidArgument" }; |
|
1587 return; |
|
1588 } |
|
1589 |
|
1590 aOwner.consoleActor.evalWindow = aWindow; |
|
1591 aOwner.helperResult = { type: "cd" }; |
|
1592 }; |
|
1593 |
|
1594 /** |
|
1595 * Inspects the passed aObject. This is done by opening the PropertyPanel. |
|
1596 * |
|
1597 * @param object aObject |
|
1598 * Object to inspect. |
|
1599 */ |
|
1600 aOwner.sandbox.inspect = function JSTH_inspect(aObject) |
|
1601 { |
|
1602 let dbgObj = aOwner.makeDebuggeeValue(aObject); |
|
1603 let grip = aOwner.createValueGrip(dbgObj); |
|
1604 aOwner.helperResult = { |
|
1605 type: "inspectObject", |
|
1606 input: aOwner.evalInput, |
|
1607 object: grip, |
|
1608 }; |
|
1609 }; |
|
1610 |
|
1611 /** |
|
1612 * Prints aObject to the output. |
|
1613 * |
|
1614 * @param object aObject |
|
1615 * Object to print to the output. |
|
1616 * @return string |
|
1617 */ |
|
1618 aOwner.sandbox.pprint = function JSTH_pprint(aObject) |
|
1619 { |
|
1620 if (aObject === null || aObject === undefined || aObject === true || |
|
1621 aObject === false) { |
|
1622 aOwner.helperResult = { |
|
1623 type: "error", |
|
1624 message: "helperFuncUnsupportedTypeError", |
|
1625 }; |
|
1626 return null; |
|
1627 } |
|
1628 |
|
1629 aOwner.helperResult = { rawOutput: true }; |
|
1630 |
|
1631 if (typeof aObject == "function") { |
|
1632 return aObject + "\n"; |
|
1633 } |
|
1634 |
|
1635 let output = []; |
|
1636 |
|
1637 let obj = WebConsoleUtils.unwrap(aObject); |
|
1638 for (let name in obj) { |
|
1639 let desc = WebConsoleUtils.getPropertyDescriptor(obj, name) || {}; |
|
1640 if (desc.get || desc.set) { |
|
1641 // TODO: Bug 842672 - toolkit/ imports modules from browser/. |
|
1642 let getGrip = VariablesView.getGrip(desc.get); |
|
1643 let setGrip = VariablesView.getGrip(desc.set); |
|
1644 let getString = VariablesView.getString(getGrip); |
|
1645 let setString = VariablesView.getString(setGrip); |
|
1646 output.push(name + ":", " get: " + getString, " set: " + setString); |
|
1647 } |
|
1648 else { |
|
1649 let valueGrip = VariablesView.getGrip(obj[name]); |
|
1650 let valueString = VariablesView.getString(valueGrip); |
|
1651 output.push(name + ": " + valueString); |
|
1652 } |
|
1653 } |
|
1654 |
|
1655 return " " + output.join("\n "); |
|
1656 }; |
|
1657 |
|
1658 /** |
|
1659 * Print a string to the output, as-is. |
|
1660 * |
|
1661 * @param string aString |
|
1662 * A string you want to output. |
|
1663 * @return void |
|
1664 */ |
|
1665 aOwner.sandbox.print = function JSTH_print(aString) |
|
1666 { |
|
1667 aOwner.helperResult = { rawOutput: true }; |
|
1668 return String(aString); |
|
1669 }; |
|
1670 } |
|
1671 exports.JSTermHelpers = JSTermHelpers; |
|
1672 |
|
1673 |
|
1674 /** |
|
1675 * A ReflowObserver that listens for reflow events from the page. |
|
1676 * Implements nsIReflowObserver. |
|
1677 * |
|
1678 * @constructor |
|
1679 * @param object aWindow |
|
1680 * The window for which we need to track reflow. |
|
1681 * @param object aOwner |
|
1682 * The listener owner which needs to implement: |
|
1683 * - onReflowActivity(aReflowInfo) |
|
1684 */ |
|
1685 |
|
1686 function ConsoleReflowListener(aWindow, aListener) |
|
1687 { |
|
1688 this.docshell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
|
1689 .getInterface(Ci.nsIWebNavigation) |
|
1690 .QueryInterface(Ci.nsIDocShell); |
|
1691 this.listener = aListener; |
|
1692 this.docshell.addWeakReflowObserver(this); |
|
1693 } |
|
1694 |
|
1695 exports.ConsoleReflowListener = ConsoleReflowListener; |
|
1696 |
|
1697 ConsoleReflowListener.prototype = |
|
1698 { |
|
1699 QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver, |
|
1700 Ci.nsISupportsWeakReference]), |
|
1701 docshell: null, |
|
1702 listener: null, |
|
1703 |
|
1704 /** |
|
1705 * Forward reflow event to listener. |
|
1706 * |
|
1707 * @param DOMHighResTimeStamp aStart |
|
1708 * @param DOMHighResTimeStamp aEnd |
|
1709 * @param boolean aInterruptible |
|
1710 */ |
|
1711 sendReflow: function CRL_sendReflow(aStart, aEnd, aInterruptible) |
|
1712 { |
|
1713 let frame = components.stack.caller.caller; |
|
1714 |
|
1715 let filename = frame.filename; |
|
1716 |
|
1717 if (filename) { |
|
1718 // Because filename could be of the form "xxx.js -> xxx.js -> xxx.js", |
|
1719 // we only take the last part. |
|
1720 filename = filename.split(" ").pop(); |
|
1721 } |
|
1722 |
|
1723 this.listener.onReflowActivity({ |
|
1724 interruptible: aInterruptible, |
|
1725 start: aStart, |
|
1726 end: aEnd, |
|
1727 sourceURL: filename, |
|
1728 sourceLine: frame.lineNumber, |
|
1729 functionName: frame.name |
|
1730 }); |
|
1731 }, |
|
1732 |
|
1733 /** |
|
1734 * On uninterruptible reflow |
|
1735 * |
|
1736 * @param DOMHighResTimeStamp aStart |
|
1737 * @param DOMHighResTimeStamp aEnd |
|
1738 */ |
|
1739 reflow: function CRL_reflow(aStart, aEnd) |
|
1740 { |
|
1741 this.sendReflow(aStart, aEnd, false); |
|
1742 }, |
|
1743 |
|
1744 /** |
|
1745 * On interruptible reflow |
|
1746 * |
|
1747 * @param DOMHighResTimeStamp aStart |
|
1748 * @param DOMHighResTimeStamp aEnd |
|
1749 */ |
|
1750 reflowInterruptible: function CRL_reflowInterruptible(aStart, aEnd) |
|
1751 { |
|
1752 this.sendReflow(aStart, aEnd, true); |
|
1753 }, |
|
1754 |
|
1755 /** |
|
1756 * Unregister listener. |
|
1757 */ |
|
1758 destroy: function CRL_destroy() |
|
1759 { |
|
1760 this.docshell.removeWeakReflowObserver(this); |
|
1761 this.listener = this.docshell = null; |
|
1762 }, |
|
1763 }; |
|
1764 |
|
1765 function gSequenceId() |
|
1766 { |
|
1767 return gSequenceId.n++; |
|
1768 } |
|
1769 gSequenceId.n = 0; |