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 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | "use strict"; |
michael@0 | 5 | |
michael@0 | 6 | const {Cc, Ci, Cu, Cr} = require("chrome"); |
michael@0 | 7 | const events = require("sdk/event/core"); |
michael@0 | 8 | const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); |
michael@0 | 9 | const protocol = require("devtools/server/protocol"); |
michael@0 | 10 | const {ContentObserver} = require("devtools/content-observer"); |
michael@0 | 11 | |
michael@0 | 12 | const {on, once, off, emit} = events; |
michael@0 | 13 | const {method, Arg, Option, RetVal} = protocol; |
michael@0 | 14 | |
michael@0 | 15 | exports.register = function(handle) { |
michael@0 | 16 | handle.addTabActor(CallWatcherActor, "callWatcherActor"); |
michael@0 | 17 | }; |
michael@0 | 18 | |
michael@0 | 19 | exports.unregister = function(handle) { |
michael@0 | 20 | handle.removeTabActor(CallWatcherActor); |
michael@0 | 21 | }; |
michael@0 | 22 | |
michael@0 | 23 | /** |
michael@0 | 24 | * Type describing a single function call in a stack trace. |
michael@0 | 25 | */ |
michael@0 | 26 | protocol.types.addDictType("call-stack-item", { |
michael@0 | 27 | name: "string", |
michael@0 | 28 | file: "string", |
michael@0 | 29 | line: "number" |
michael@0 | 30 | }); |
michael@0 | 31 | |
michael@0 | 32 | /** |
michael@0 | 33 | * Type describing an overview of a function call. |
michael@0 | 34 | */ |
michael@0 | 35 | protocol.types.addDictType("call-details", { |
michael@0 | 36 | type: "number", |
michael@0 | 37 | name: "string", |
michael@0 | 38 | stack: "array:call-stack-item" |
michael@0 | 39 | }); |
michael@0 | 40 | |
michael@0 | 41 | /** |
michael@0 | 42 | * This actor contains information about a function call, like the function |
michael@0 | 43 | * type, name, stack, arguments, returned value etc. |
michael@0 | 44 | */ |
michael@0 | 45 | let FunctionCallActor = protocol.ActorClass({ |
michael@0 | 46 | typeName: "function-call", |
michael@0 | 47 | |
michael@0 | 48 | /** |
michael@0 | 49 | * Creates the function call actor. |
michael@0 | 50 | * |
michael@0 | 51 | * @param DebuggerServerConnection conn |
michael@0 | 52 | * The server connection. |
michael@0 | 53 | * @param DOMWindow window |
michael@0 | 54 | * The content window. |
michael@0 | 55 | * @param string global |
michael@0 | 56 | * The name of the global object owning this function, like |
michael@0 | 57 | * "CanvasRenderingContext2D" or "WebGLRenderingContext". |
michael@0 | 58 | * @param object caller |
michael@0 | 59 | * The object owning the function when it was called. |
michael@0 | 60 | * For example, in `foo.bar()`, the caller is `foo`. |
michael@0 | 61 | * @param number type |
michael@0 | 62 | * Either METHOD_FUNCTION, METHOD_GETTER or METHOD_SETTER. |
michael@0 | 63 | * @param string name |
michael@0 | 64 | * The called function's name. |
michael@0 | 65 | * @param array stack |
michael@0 | 66 | * The called function's stack, as a list of { name, file, line } objects. |
michael@0 | 67 | * @param array args |
michael@0 | 68 | * The called function's arguments. |
michael@0 | 69 | * @param any result |
michael@0 | 70 | * The value returned by the function call. |
michael@0 | 71 | */ |
michael@0 | 72 | initialize: function(conn, [window, global, caller, type, name, stack, args, result]) { |
michael@0 | 73 | protocol.Actor.prototype.initialize.call(this, conn); |
michael@0 | 74 | |
michael@0 | 75 | this.details = { |
michael@0 | 76 | window: window, |
michael@0 | 77 | caller: caller, |
michael@0 | 78 | type: type, |
michael@0 | 79 | name: name, |
michael@0 | 80 | stack: stack, |
michael@0 | 81 | args: args, |
michael@0 | 82 | result: result |
michael@0 | 83 | }; |
michael@0 | 84 | |
michael@0 | 85 | this.meta = { |
michael@0 | 86 | global: -1, |
michael@0 | 87 | previews: { caller: "", args: "" } |
michael@0 | 88 | }; |
michael@0 | 89 | |
michael@0 | 90 | if (global == "WebGLRenderingContext") { |
michael@0 | 91 | this.meta.global = CallWatcherFront.CANVAS_WEBGL_CONTEXT; |
michael@0 | 92 | } else if (global == "CanvasRenderingContext2D") { |
michael@0 | 93 | this.meta.global = CallWatcherFront.CANVAS_2D_CONTEXT; |
michael@0 | 94 | } else if (global == "window") { |
michael@0 | 95 | this.meta.global = CallWatcherFront.UNKNOWN_SCOPE; |
michael@0 | 96 | } else { |
michael@0 | 97 | this.meta.global = CallWatcherFront.GLOBAL_SCOPE; |
michael@0 | 98 | } |
michael@0 | 99 | |
michael@0 | 100 | this.meta.previews.caller = this._generateCallerPreview(); |
michael@0 | 101 | this.meta.previews.args = this._generateArgsPreview(); |
michael@0 | 102 | }, |
michael@0 | 103 | |
michael@0 | 104 | /** |
michael@0 | 105 | * Customize the marshalling of this actor to provide some generic information |
michael@0 | 106 | * directly on the Front instance. |
michael@0 | 107 | */ |
michael@0 | 108 | form: function() { |
michael@0 | 109 | return { |
michael@0 | 110 | actor: this.actorID, |
michael@0 | 111 | type: this.details.type, |
michael@0 | 112 | name: this.details.name, |
michael@0 | 113 | file: this.details.stack[0].file, |
michael@0 | 114 | line: this.details.stack[0].line, |
michael@0 | 115 | callerPreview: this.meta.previews.caller, |
michael@0 | 116 | argsPreview: this.meta.previews.args |
michael@0 | 117 | }; |
michael@0 | 118 | }, |
michael@0 | 119 | |
michael@0 | 120 | /** |
michael@0 | 121 | * Gets more information about this function call, which is not necessarily |
michael@0 | 122 | * available on the Front instance. |
michael@0 | 123 | */ |
michael@0 | 124 | getDetails: method(function() { |
michael@0 | 125 | let { type, name, stack } = this.details; |
michael@0 | 126 | |
michael@0 | 127 | // Since not all calls on the stack have corresponding owner files (e.g. |
michael@0 | 128 | // callbacks of a requestAnimationFrame etc.), there's no benefit in |
michael@0 | 129 | // returning them, as the user can't jump to the Debugger from them. |
michael@0 | 130 | for (let i = stack.length - 1;;) { |
michael@0 | 131 | if (stack[i].file) { |
michael@0 | 132 | break; |
michael@0 | 133 | } |
michael@0 | 134 | stack.pop(); |
michael@0 | 135 | i--; |
michael@0 | 136 | } |
michael@0 | 137 | |
michael@0 | 138 | // XXX: Use grips for objects and serialize them properly, in order |
michael@0 | 139 | // to add the function's caller, arguments and return value. Bug 978957. |
michael@0 | 140 | return { |
michael@0 | 141 | type: type, |
michael@0 | 142 | name: name, |
michael@0 | 143 | stack: stack |
michael@0 | 144 | }; |
michael@0 | 145 | }, { |
michael@0 | 146 | response: { info: RetVal("call-details") } |
michael@0 | 147 | }), |
michael@0 | 148 | |
michael@0 | 149 | /** |
michael@0 | 150 | * Serializes the caller's name so that it can be easily be transferred |
michael@0 | 151 | * as a string, but still be useful when displayed in a potential UI. |
michael@0 | 152 | * |
michael@0 | 153 | * @return string |
michael@0 | 154 | * The caller's name as a string. |
michael@0 | 155 | */ |
michael@0 | 156 | _generateCallerPreview: function() { |
michael@0 | 157 | let global = this.meta.global; |
michael@0 | 158 | if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) { |
michael@0 | 159 | return "gl"; |
michael@0 | 160 | } |
michael@0 | 161 | if (global == CallWatcherFront.CANVAS_2D_CONTEXT) { |
michael@0 | 162 | return "ctx"; |
michael@0 | 163 | } |
michael@0 | 164 | return ""; |
michael@0 | 165 | }, |
michael@0 | 166 | |
michael@0 | 167 | /** |
michael@0 | 168 | * Serializes the arguments so that they can be easily be transferred |
michael@0 | 169 | * as a string, but still be useful when displayed in a potential UI. |
michael@0 | 170 | * |
michael@0 | 171 | * @return string |
michael@0 | 172 | * The arguments as a string. |
michael@0 | 173 | */ |
michael@0 | 174 | _generateArgsPreview: function() { |
michael@0 | 175 | let { caller, args } = this.details; |
michael@0 | 176 | let { global } = this.meta; |
michael@0 | 177 | |
michael@0 | 178 | // XXX: All of this sucks. Make this smarter, so that the frontend |
michael@0 | 179 | // can inspect each argument, be it object or primitive. Bug 978960. |
michael@0 | 180 | let serializeArgs = () => args.map(arg => { |
michael@0 | 181 | if (typeof arg == "undefined") { |
michael@0 | 182 | return "undefined"; |
michael@0 | 183 | } |
michael@0 | 184 | if (typeof arg == "function") { |
michael@0 | 185 | return "Function"; |
michael@0 | 186 | } |
michael@0 | 187 | if (typeof arg == "object") { |
michael@0 | 188 | return "Object"; |
michael@0 | 189 | } |
michael@0 | 190 | if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) { |
michael@0 | 191 | // XXX: This doesn't handle combined bitmasks. Bug 978964. |
michael@0 | 192 | return getEnumsLookupTable("webgl", caller)[arg] || arg; |
michael@0 | 193 | } |
michael@0 | 194 | if (global == CallWatcherFront.CANVAS_2D_CONTEXT) { |
michael@0 | 195 | return getEnumsLookupTable("2d", caller)[arg] || arg; |
michael@0 | 196 | } |
michael@0 | 197 | return arg; |
michael@0 | 198 | }); |
michael@0 | 199 | |
michael@0 | 200 | return serializeArgs().join(", "); |
michael@0 | 201 | } |
michael@0 | 202 | }); |
michael@0 | 203 | |
michael@0 | 204 | /** |
michael@0 | 205 | * The corresponding Front object for the FunctionCallActor. |
michael@0 | 206 | */ |
michael@0 | 207 | let FunctionCallFront = protocol.FrontClass(FunctionCallActor, { |
michael@0 | 208 | initialize: function(client, form) { |
michael@0 | 209 | protocol.Front.prototype.initialize.call(this, client, form); |
michael@0 | 210 | }, |
michael@0 | 211 | |
michael@0 | 212 | /** |
michael@0 | 213 | * Adds some generic information directly to this instance, |
michael@0 | 214 | * to avoid extra roundtrips. |
michael@0 | 215 | */ |
michael@0 | 216 | form: function(form) { |
michael@0 | 217 | this.actorID = form.actor; |
michael@0 | 218 | this.type = form.type; |
michael@0 | 219 | this.name = form.name; |
michael@0 | 220 | this.file = form.file; |
michael@0 | 221 | this.line = form.line; |
michael@0 | 222 | this.callerPreview = form.callerPreview; |
michael@0 | 223 | this.argsPreview = form.argsPreview; |
michael@0 | 224 | } |
michael@0 | 225 | }); |
michael@0 | 226 | |
michael@0 | 227 | /** |
michael@0 | 228 | * This actor observes function calls on certain objects or globals. |
michael@0 | 229 | */ |
michael@0 | 230 | let CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({ |
michael@0 | 231 | typeName: "call-watcher", |
michael@0 | 232 | initialize: function(conn, tabActor) { |
michael@0 | 233 | protocol.Actor.prototype.initialize.call(this, conn); |
michael@0 | 234 | this.tabActor = tabActor; |
michael@0 | 235 | this._onGlobalCreated = this._onGlobalCreated.bind(this); |
michael@0 | 236 | this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this); |
michael@0 | 237 | this._onContentFunctionCall = this._onContentFunctionCall.bind(this); |
michael@0 | 238 | }, |
michael@0 | 239 | destroy: function(conn) { |
michael@0 | 240 | protocol.Actor.prototype.destroy.call(this, conn); |
michael@0 | 241 | this.finalize(); |
michael@0 | 242 | }, |
michael@0 | 243 | |
michael@0 | 244 | /** |
michael@0 | 245 | * Starts waiting for the current tab actor's document global to be |
michael@0 | 246 | * created, in order to instrument the specified objects and become |
michael@0 | 247 | * aware of everything the content does with them. |
michael@0 | 248 | */ |
michael@0 | 249 | setup: method(function({ tracedGlobals, tracedFunctions, startRecording, performReload }) { |
michael@0 | 250 | if (this._initialized) { |
michael@0 | 251 | return; |
michael@0 | 252 | } |
michael@0 | 253 | this._initialized = true; |
michael@0 | 254 | |
michael@0 | 255 | this._functionCalls = []; |
michael@0 | 256 | this._tracedGlobals = tracedGlobals || []; |
michael@0 | 257 | this._tracedFunctions = tracedFunctions || []; |
michael@0 | 258 | this._contentObserver = new ContentObserver(this.tabActor); |
michael@0 | 259 | |
michael@0 | 260 | on(this._contentObserver, "global-created", this._onGlobalCreated); |
michael@0 | 261 | on(this._contentObserver, "global-destroyed", this._onGlobalDestroyed); |
michael@0 | 262 | |
michael@0 | 263 | if (startRecording) { |
michael@0 | 264 | this.resumeRecording(); |
michael@0 | 265 | } |
michael@0 | 266 | if (performReload) { |
michael@0 | 267 | this.tabActor.window.location.reload(); |
michael@0 | 268 | } |
michael@0 | 269 | }, { |
michael@0 | 270 | request: { |
michael@0 | 271 | tracedGlobals: Option(0, "nullable:array:string"), |
michael@0 | 272 | tracedFunctions: Option(0, "nullable:array:string"), |
michael@0 | 273 | startRecording: Option(0, "boolean"), |
michael@0 | 274 | performReload: Option(0, "boolean") |
michael@0 | 275 | }, |
michael@0 | 276 | oneway: true |
michael@0 | 277 | }), |
michael@0 | 278 | |
michael@0 | 279 | /** |
michael@0 | 280 | * Stops listening for document global changes and puts this actor |
michael@0 | 281 | * to hibernation. This method is called automatically just before the |
michael@0 | 282 | * actor is destroyed. |
michael@0 | 283 | */ |
michael@0 | 284 | finalize: method(function() { |
michael@0 | 285 | if (!this._initialized) { |
michael@0 | 286 | return; |
michael@0 | 287 | } |
michael@0 | 288 | this._initialized = false; |
michael@0 | 289 | |
michael@0 | 290 | this._contentObserver.stopListening(); |
michael@0 | 291 | off(this._contentObserver, "global-created", this._onGlobalCreated); |
michael@0 | 292 | off(this._contentObserver, "global-destroyed", this._onGlobalDestroyed); |
michael@0 | 293 | |
michael@0 | 294 | this._tracedGlobals = null; |
michael@0 | 295 | this._tracedFunctions = null; |
michael@0 | 296 | this._contentObserver = null; |
michael@0 | 297 | }, { |
michael@0 | 298 | oneway: true |
michael@0 | 299 | }), |
michael@0 | 300 | |
michael@0 | 301 | /** |
michael@0 | 302 | * Returns whether the instrumented function calls are currently recorded. |
michael@0 | 303 | */ |
michael@0 | 304 | isRecording: method(function() { |
michael@0 | 305 | return this._recording; |
michael@0 | 306 | }, { |
michael@0 | 307 | response: RetVal("boolean") |
michael@0 | 308 | }), |
michael@0 | 309 | |
michael@0 | 310 | /** |
michael@0 | 311 | * Starts recording function calls. |
michael@0 | 312 | */ |
michael@0 | 313 | resumeRecording: method(function() { |
michael@0 | 314 | this._recording = true; |
michael@0 | 315 | }), |
michael@0 | 316 | |
michael@0 | 317 | /** |
michael@0 | 318 | * Stops recording function calls. |
michael@0 | 319 | */ |
michael@0 | 320 | pauseRecording: method(function() { |
michael@0 | 321 | this._recording = false; |
michael@0 | 322 | return this._functionCalls; |
michael@0 | 323 | }, { |
michael@0 | 324 | response: { calls: RetVal("array:function-call") } |
michael@0 | 325 | }), |
michael@0 | 326 | |
michael@0 | 327 | /** |
michael@0 | 328 | * Erases all the recorded function calls. |
michael@0 | 329 | * Calling `resumeRecording` or `pauseRecording` does not erase history. |
michael@0 | 330 | */ |
michael@0 | 331 | eraseRecording: method(function() { |
michael@0 | 332 | this._functionCalls = []; |
michael@0 | 333 | }), |
michael@0 | 334 | |
michael@0 | 335 | /** |
michael@0 | 336 | * Lightweight listener invoked whenever an instrumented function is called |
michael@0 | 337 | * while recording. We're doing this to avoid the event emitter overhead, |
michael@0 | 338 | * since this is expected to be a very hot function. |
michael@0 | 339 | */ |
michael@0 | 340 | onCall: function() {}, |
michael@0 | 341 | |
michael@0 | 342 | /** |
michael@0 | 343 | * Invoked whenever the current tab actor's document global is created. |
michael@0 | 344 | */ |
michael@0 | 345 | _onGlobalCreated: function(window) { |
michael@0 | 346 | let self = this; |
michael@0 | 347 | |
michael@0 | 348 | this._tracedWindowId = ContentObserver.GetInnerWindowID(window); |
michael@0 | 349 | let unwrappedWindow = XPCNativeWrapper.unwrap(window); |
michael@0 | 350 | let callback = this._onContentFunctionCall; |
michael@0 | 351 | |
michael@0 | 352 | for (let global of this._tracedGlobals) { |
michael@0 | 353 | let prototype = unwrappedWindow[global].prototype; |
michael@0 | 354 | let properties = Object.keys(prototype); |
michael@0 | 355 | properties.forEach(name => overrideSymbol(global, prototype, name, callback)); |
michael@0 | 356 | } |
michael@0 | 357 | |
michael@0 | 358 | for (let name of this._tracedFunctions) { |
michael@0 | 359 | overrideSymbol("window", unwrappedWindow, name, callback); |
michael@0 | 360 | } |
michael@0 | 361 | |
michael@0 | 362 | /** |
michael@0 | 363 | * Instruments a method, getter or setter on the specified target object to |
michael@0 | 364 | * invoke a callback whenever it is called. |
michael@0 | 365 | */ |
michael@0 | 366 | function overrideSymbol(global, target, name, callback) { |
michael@0 | 367 | let propertyDescriptor = Object.getOwnPropertyDescriptor(target, name); |
michael@0 | 368 | |
michael@0 | 369 | if (propertyDescriptor.get || propertyDescriptor.set) { |
michael@0 | 370 | overrideAccessor(global, target, name, propertyDescriptor, callback); |
michael@0 | 371 | return; |
michael@0 | 372 | } |
michael@0 | 373 | if (propertyDescriptor.writable && typeof propertyDescriptor.value == "function") { |
michael@0 | 374 | overrideFunction(global, target, name, propertyDescriptor, callback); |
michael@0 | 375 | return; |
michael@0 | 376 | } |
michael@0 | 377 | } |
michael@0 | 378 | |
michael@0 | 379 | /** |
michael@0 | 380 | * Instruments a function on the specified target object. |
michael@0 | 381 | */ |
michael@0 | 382 | function overrideFunction(global, target, name, descriptor, callback) { |
michael@0 | 383 | let originalFunc = target[name]; |
michael@0 | 384 | |
michael@0 | 385 | Object.defineProperty(target, name, { |
michael@0 | 386 | value: function(...args) { |
michael@0 | 387 | let result = originalFunc.apply(this, args); |
michael@0 | 388 | |
michael@0 | 389 | if (self._recording) { |
michael@0 | 390 | let stack = getStack(name); |
michael@0 | 391 | let type = CallWatcherFront.METHOD_FUNCTION; |
michael@0 | 392 | callback(unwrappedWindow, global, this, type, name, stack, args, result); |
michael@0 | 393 | } |
michael@0 | 394 | return result; |
michael@0 | 395 | }, |
michael@0 | 396 | configurable: descriptor.configurable, |
michael@0 | 397 | enumerable: descriptor.enumerable, |
michael@0 | 398 | writable: true |
michael@0 | 399 | }); |
michael@0 | 400 | } |
michael@0 | 401 | |
michael@0 | 402 | /** |
michael@0 | 403 | * Instruments a getter or setter on the specified target object. |
michael@0 | 404 | */ |
michael@0 | 405 | function overrideAccessor(global, target, name, descriptor, callback) { |
michael@0 | 406 | let originalGetter = target.__lookupGetter__(name); |
michael@0 | 407 | let originalSetter = target.__lookupSetter__(name); |
michael@0 | 408 | |
michael@0 | 409 | Object.defineProperty(target, name, { |
michael@0 | 410 | get: function(...args) { |
michael@0 | 411 | if (!originalGetter) return undefined; |
michael@0 | 412 | let result = originalGetter.apply(this, args); |
michael@0 | 413 | |
michael@0 | 414 | if (self._recording) { |
michael@0 | 415 | let stack = getStack(name); |
michael@0 | 416 | let type = CallWatcherFront.GETTER_FUNCTION; |
michael@0 | 417 | callback(unwrappedWindow, global, this, type, name, stack, args, result); |
michael@0 | 418 | } |
michael@0 | 419 | return result; |
michael@0 | 420 | }, |
michael@0 | 421 | set: function(...args) { |
michael@0 | 422 | if (!originalSetter) return; |
michael@0 | 423 | originalSetter.apply(this, args); |
michael@0 | 424 | |
michael@0 | 425 | if (self._recording) { |
michael@0 | 426 | let stack = getStack(name); |
michael@0 | 427 | let type = CallWatcherFront.SETTER_FUNCTION; |
michael@0 | 428 | callback(unwrappedWindow, global, this, type, name, stack, args, undefined); |
michael@0 | 429 | } |
michael@0 | 430 | }, |
michael@0 | 431 | configurable: descriptor.configurable, |
michael@0 | 432 | enumerable: descriptor.enumerable |
michael@0 | 433 | }); |
michael@0 | 434 | } |
michael@0 | 435 | |
michael@0 | 436 | /** |
michael@0 | 437 | * Stores the relevant information about calls on the stack when |
michael@0 | 438 | * a function is called. |
michael@0 | 439 | */ |
michael@0 | 440 | function getStack(caller) { |
michael@0 | 441 | try { |
michael@0 | 442 | // Using Components.stack wouldn't be a better idea, since it's |
michael@0 | 443 | // much slower because it attempts to retrieve the C++ stack as well. |
michael@0 | 444 | throw new Error(); |
michael@0 | 445 | } catch (e) { |
michael@0 | 446 | var stack = e.stack; |
michael@0 | 447 | } |
michael@0 | 448 | |
michael@0 | 449 | // Of course, using a simple regex like /(.*?)@(.*):(\d*):\d*/ would be |
michael@0 | 450 | // much prettier, but this is a very hot function, so let's sqeeze |
michael@0 | 451 | // every drop of performance out of it. |
michael@0 | 452 | let calls = []; |
michael@0 | 453 | let callIndex = 0; |
michael@0 | 454 | let currNewLinePivot = stack.indexOf("\n") + 1; |
michael@0 | 455 | let nextNewLinePivot = stack.indexOf("\n", currNewLinePivot); |
michael@0 | 456 | |
michael@0 | 457 | while (nextNewLinePivot > 0) { |
michael@0 | 458 | let nameDelimiterIndex = stack.indexOf("@", currNewLinePivot); |
michael@0 | 459 | let columnDelimiterIndex = stack.lastIndexOf(":", nextNewLinePivot - 1); |
michael@0 | 460 | let lineDelimiterIndex = stack.lastIndexOf(":", columnDelimiterIndex - 1); |
michael@0 | 461 | |
michael@0 | 462 | if (!calls[callIndex]) { |
michael@0 | 463 | calls[callIndex] = { name: "", file: "", line: 0 }; |
michael@0 | 464 | } |
michael@0 | 465 | if (!calls[callIndex + 1]) { |
michael@0 | 466 | calls[callIndex + 1] = { name: "", file: "", line: 0 }; |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | if (callIndex > 0) { |
michael@0 | 470 | let file = stack.substring(nameDelimiterIndex + 1, lineDelimiterIndex); |
michael@0 | 471 | let line = stack.substring(lineDelimiterIndex + 1, columnDelimiterIndex); |
michael@0 | 472 | let name = stack.substring(currNewLinePivot, nameDelimiterIndex); |
michael@0 | 473 | calls[callIndex].name = name; |
michael@0 | 474 | calls[callIndex - 1].file = file; |
michael@0 | 475 | calls[callIndex - 1].line = line; |
michael@0 | 476 | } else { |
michael@0 | 477 | // Since the topmost stack frame is actually our overwritten function, |
michael@0 | 478 | // it will not have the expected name. |
michael@0 | 479 | calls[0].name = caller; |
michael@0 | 480 | } |
michael@0 | 481 | |
michael@0 | 482 | currNewLinePivot = nextNewLinePivot + 1; |
michael@0 | 483 | nextNewLinePivot = stack.indexOf("\n", currNewLinePivot); |
michael@0 | 484 | callIndex++; |
michael@0 | 485 | } |
michael@0 | 486 | |
michael@0 | 487 | return calls; |
michael@0 | 488 | } |
michael@0 | 489 | }, |
michael@0 | 490 | |
michael@0 | 491 | /** |
michael@0 | 492 | * Invoked whenever the current tab actor's inner window is destroyed. |
michael@0 | 493 | */ |
michael@0 | 494 | _onGlobalDestroyed: function(id) { |
michael@0 | 495 | if (this._tracedWindowId == id) { |
michael@0 | 496 | this.pauseRecording(); |
michael@0 | 497 | this.eraseRecording(); |
michael@0 | 498 | } |
michael@0 | 499 | }, |
michael@0 | 500 | |
michael@0 | 501 | /** |
michael@0 | 502 | * Invoked whenever an instrumented function is called. |
michael@0 | 503 | */ |
michael@0 | 504 | _onContentFunctionCall: function(...details) { |
michael@0 | 505 | let functionCall = new FunctionCallActor(this.conn, details); |
michael@0 | 506 | this._functionCalls.push(functionCall); |
michael@0 | 507 | this.onCall(functionCall); |
michael@0 | 508 | } |
michael@0 | 509 | }); |
michael@0 | 510 | |
michael@0 | 511 | /** |
michael@0 | 512 | * The corresponding Front object for the CallWatcherActor. |
michael@0 | 513 | */ |
michael@0 | 514 | let CallWatcherFront = exports.CallWatcherFront = protocol.FrontClass(CallWatcherActor, { |
michael@0 | 515 | initialize: function(client, { callWatcherActor }) { |
michael@0 | 516 | protocol.Front.prototype.initialize.call(this, client, { actor: callWatcherActor }); |
michael@0 | 517 | client.addActorPool(this); |
michael@0 | 518 | this.manage(this); |
michael@0 | 519 | } |
michael@0 | 520 | }); |
michael@0 | 521 | |
michael@0 | 522 | /** |
michael@0 | 523 | * Constants. |
michael@0 | 524 | */ |
michael@0 | 525 | CallWatcherFront.METHOD_FUNCTION = 0; |
michael@0 | 526 | CallWatcherFront.GETTER_FUNCTION = 1; |
michael@0 | 527 | CallWatcherFront.SETTER_FUNCTION = 2; |
michael@0 | 528 | |
michael@0 | 529 | CallWatcherFront.GLOBAL_SCOPE = 0; |
michael@0 | 530 | CallWatcherFront.UNKNOWN_SCOPE = 1; |
michael@0 | 531 | CallWatcherFront.CANVAS_WEBGL_CONTEXT = 2; |
michael@0 | 532 | CallWatcherFront.CANVAS_2D_CONTEXT = 3; |
michael@0 | 533 | |
michael@0 | 534 | /** |
michael@0 | 535 | * A lookup table for cross-referencing flags or properties with their name |
michael@0 | 536 | * assuming they look LIKE_THIS most of the time. |
michael@0 | 537 | * |
michael@0 | 538 | * For example, when gl.clear(gl.COLOR_BUFFER_BIT) is called, the actual passed |
michael@0 | 539 | * argument's value is 16384, which we want identified as "COLOR_BUFFER_BIT". |
michael@0 | 540 | */ |
michael@0 | 541 | var gEnumRegex = /^[A-Z_]+$/; |
michael@0 | 542 | var gEnumsLookupTable = {}; |
michael@0 | 543 | |
michael@0 | 544 | function getEnumsLookupTable(type, object) { |
michael@0 | 545 | let cachedEnum = gEnumsLookupTable[type]; |
michael@0 | 546 | if (cachedEnum) { |
michael@0 | 547 | return cachedEnum; |
michael@0 | 548 | } |
michael@0 | 549 | |
michael@0 | 550 | let table = gEnumsLookupTable[type] = {}; |
michael@0 | 551 | |
michael@0 | 552 | for (let key in object) { |
michael@0 | 553 | if (key.match(gEnumRegex)) { |
michael@0 | 554 | table[object[key]] = key; |
michael@0 | 555 | } |
michael@0 | 556 | } |
michael@0 | 557 | |
michael@0 | 558 | return table; |
michael@0 | 559 | } |