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