1 /* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 (function () { 32 33 var DebuggerScript = {}; 34 35 DebuggerScript.PauseOnExceptionsState = { 36 DontPauseOnExceptions: 0, 37 PauseOnAllExceptions: 1, 38 PauseOnUncaughtExceptions: 2 39 }; 40 41 DebuggerScript.ScopeInfoDetails = { 42 AllScopes: 0, 43 FastAsyncScopes: 1, 44 NoScopes: 2 45 }; 46 47 DebuggerScript._pauseOnExceptionsState = DebuggerScript.PauseOnExceptionsState.DontPauseOnExceptions; 48 Debug.clearBreakOnException(); 49 Debug.clearBreakOnUncaughtException(); 50 51 DebuggerScript.getAfterCompileScript = function(eventData) 52 { 53 return DebuggerScript._formatScript(eventData.script_.script_); 54 } 55 56 DebuggerScript.getWorkerScripts = function() 57 { 58 var result = []; 59 var scripts = Debug.scripts(); 60 for (var i = 0; i < scripts.length; ++i) { 61 var script = scripts[i]; 62 // Workers don't share same V8 heap now so there is no need to complicate stuff with 63 // the context id like we do to discriminate between scripts from different pages. 64 // However we need to filter out v8 native scripts. 65 if (script.context_data && script.context_data === "worker") 66 result.push(DebuggerScript._formatScript(script)); 67 } 68 return result; 69 } 70 71 DebuggerScript.getFunctionScopes = function(fun) 72 { 73 var mirror = MakeMirror(fun); 74 var count = mirror.scopeCount(); 75 if (count == 0) 76 return null; 77 var result = []; 78 for (var i = 0; i < count; i++) { 79 var scopeDetails = mirror.scope(i).details(); 80 result[i] = { 81 type: scopeDetails.type(), 82 object: DebuggerScript._buildScopeObject(scopeDetails.type(), scopeDetails.object()) 83 }; 84 } 85 return result; 86 } 87 88 DebuggerScript.getCollectionEntries = function(object) 89 { 90 var mirror = MakeMirror(object, true /* transient */); 91 if (mirror.isMap()) 92 return mirror.entries(); 93 if (mirror.isSet()) { 94 var result = []; 95 var values = mirror.values(); 96 for (var i = 0; i < values.length; ++i) 97 result.push({ value: values[i] }); 98 return result; 99 } 100 } 101 102 DebuggerScript.getInternalProperties = function(value) 103 { 104 var properties = ObjectMirror.GetInternalProperties(value); 105 var result = []; 106 for (var i = 0; i < properties.length; i++) { 107 var mirror = properties[i]; 108 result.push({ 109 name: mirror.name(), 110 value: mirror.value().value() 111 }); 112 } 113 return result; 114 } 115 116 DebuggerScript.setFunctionVariableValue = function(functionValue, scopeIndex, variableName, newValue) 117 { 118 var mirror = MakeMirror(functionValue); 119 if (!mirror.isFunction()) 120 throw new Error("Function value has incorrect type"); 121 return DebuggerScript._setScopeVariableValue(mirror, scopeIndex, variableName, newValue); 122 } 123 124 DebuggerScript._setScopeVariableValue = function(scopeHolder, scopeIndex, variableName, newValue) 125 { 126 var scopeMirror = scopeHolder.scope(scopeIndex); 127 if (!scopeMirror) 128 throw new Error("Incorrect scope index"); 129 scopeMirror.setVariableValue(variableName, newValue); 130 return undefined; 131 } 132 133 DebuggerScript.getScripts = function(contextData) 134 { 135 var result = []; 136 137 if (!contextData) 138 return result; 139 var comma = contextData.indexOf(","); 140 if (comma === -1) 141 return result; 142 // Context data is a string in the following format: 143 // ("page"|"injected")","<page id> 144 var idSuffix = contextData.substring(comma); // including the comma 145 146 var scripts = Debug.scripts(); 147 for (var i = 0; i < scripts.length; ++i) { 148 var script = scripts[i]; 149 if (script.context_data && script.context_data.lastIndexOf(idSuffix) != -1) 150 result.push(DebuggerScript._formatScript(script)); 151 } 152 return result; 153 } 154 155 DebuggerScript._formatScript = function(script) 156 { 157 var lineEnds = script.line_ends; 158 var lineCount = lineEnds.length; 159 var endLine = script.line_offset + lineCount - 1; 160 var endColumn; 161 // V8 will not count last line if script source ends with \n. 162 if (script.source[script.source.length - 1] === '\n') { 163 endLine += 1; 164 endColumn = 0; 165 } else { 166 if (lineCount === 1) 167 endColumn = script.source.length + script.column_offset; 168 else 169 endColumn = script.source.length - (lineEnds[lineCount - 2] + 1); 170 } 171 172 return { 173 id: script.id, 174 name: script.nameOrSourceURL(), 175 sourceURL: script.source_url, 176 sourceMappingURL: script.source_mapping_url, 177 source: script.source, 178 startLine: script.line_offset, 179 startColumn: script.column_offset, 180 endLine: endLine, 181 endColumn: endColumn, 182 isContentScript: !!script.context_data && script.context_data.indexOf("injected") == 0 183 }; 184 } 185 186 DebuggerScript.setBreakpoint = function(execState, info) 187 { 188 var positionAlignment = info.interstatementLocation ? Debug.BreakPositionAlignment.BreakPosition : Debug.BreakPositionAlignment.Statement; 189 var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, positionAlignment); 190 191 var locations = Debug.findBreakPointActualLocations(breakId); 192 if (!locations.length) 193 return undefined; 194 info.lineNumber = locations[0].line; 195 info.columnNumber = locations[0].column; 196 return breakId.toString(); 197 } 198 199 DebuggerScript.removeBreakpoint = function(execState, info) 200 { 201 Debug.findBreakPoint(info.breakpointId, true); 202 } 203 204 DebuggerScript.pauseOnExceptionsState = function() 205 { 206 return DebuggerScript._pauseOnExceptionsState; 207 } 208 209 DebuggerScript.setPauseOnExceptionsState = function(newState) 210 { 211 DebuggerScript._pauseOnExceptionsState = newState; 212 213 if (DebuggerScript.PauseOnExceptionsState.PauseOnAllExceptions === newState) 214 Debug.setBreakOnException(); 215 else 216 Debug.clearBreakOnException(); 217 218 if (DebuggerScript.PauseOnExceptionsState.PauseOnUncaughtExceptions === newState) 219 Debug.setBreakOnUncaughtException(); 220 else 221 Debug.clearBreakOnUncaughtException(); 222 } 223 224 DebuggerScript.frameCount = function(execState) 225 { 226 return execState.frameCount(); 227 } 228 229 DebuggerScript.currentCallFrame = function(execState, data) 230 { 231 var maximumLimit = data >> 2; 232 var scopeDetailsLevel = data & 3; 233 234 var frameCount = execState.frameCount(); 235 if (maximumLimit && maximumLimit < frameCount) 236 frameCount = maximumLimit; 237 var topFrame = undefined; 238 for (var i = frameCount - 1; i >= 0; i--) { 239 var frameMirror = execState.frame(i); 240 topFrame = DebuggerScript._frameMirrorToJSCallFrame(frameMirror, topFrame, scopeDetailsLevel); 241 } 242 return topFrame; 243 } 244 245 DebuggerScript.currentCallFrameByIndex = function(execState, index) 246 { 247 if (index < 0) 248 return undefined; 249 var frameCount = execState.frameCount(); 250 if (index >= frameCount) 251 return undefined; 252 return DebuggerScript._frameMirrorToJSCallFrame(execState.frame(index), undefined, DebuggerScript.ScopeInfoDetails.NoScopes); 253 } 254 255 DebuggerScript.stepIntoStatement = function(execState) 256 { 257 execState.prepareStep(Debug.StepAction.StepIn, 1); 258 } 259 260 DebuggerScript.stepOverStatement = function(execState, callFrame) 261 { 262 execState.prepareStep(Debug.StepAction.StepNext, 1); 263 } 264 265 DebuggerScript.stepOutOfFunction = function(execState, callFrame) 266 { 267 execState.prepareStep(Debug.StepAction.StepOut, 1); 268 } 269 270 // Returns array in form: 271 // [ 0, <v8_result_report> ] in case of success 272 // or [ 1, <general_error_message>, <compiler_message>, <line_number>, <column_number> ] in case of compile error, numbers are 1-based. 273 // or throws exception with message. 274 DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview) 275 { 276 var scripts = Debug.scripts(); 277 var scriptToEdit = null; 278 for (var i = 0; i < scripts.length; i++) { 279 if (scripts[i].id == scriptId) { 280 scriptToEdit = scripts[i]; 281 break; 282 } 283 } 284 if (!scriptToEdit) 285 throw("Script not found"); 286 287 var changeLog = []; 288 try { 289 var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog); 290 return [0, result]; 291 } catch (e) { 292 if (e instanceof Debug.LiveEdit.Failure && "details" in e) { 293 var details = e.details; 294 if (details.type === "liveedit_compile_error") { 295 var startPosition = details.position.start; 296 return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)]; 297 } 298 } 299 throw e; 300 } 301 } 302 303 DebuggerScript.clearBreakpoints = function(execState, info) 304 { 305 Debug.clearAllBreakPoints(); 306 } 307 308 DebuggerScript.setBreakpointsActivated = function(execState, info) 309 { 310 Debug.debuggerFlags().breakPointsActive.setValue(info.enabled); 311 } 312 313 DebuggerScript.getScriptSource = function(eventData) 314 { 315 return eventData.script().source(); 316 } 317 318 DebuggerScript.setScriptSource = function(eventData, source) 319 { 320 if (eventData.script().data() === "injected-script") 321 return; 322 eventData.script().setSource(source); 323 } 324 325 DebuggerScript.getScriptName = function(eventData) 326 { 327 return eventData.script().script_.nameOrSourceURL(); 328 } 329 330 DebuggerScript.getBreakpointNumbers = function(eventData) 331 { 332 var breakpoints = eventData.breakPointsHit(); 333 var numbers = []; 334 if (!breakpoints) 335 return numbers; 336 337 for (var i = 0; i < breakpoints.length; i++) { 338 var breakpoint = breakpoints[i]; 339 var scriptBreakPoint = breakpoint.script_break_point(); 340 numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number()); 341 } 342 return numbers; 343 } 344 345 DebuggerScript.isEvalCompilation = function(eventData) 346 { 347 var script = eventData.script(); 348 return (script.compilationType() === Debug.ScriptCompilationType.Eval); 349 } 350 351 // NOTE: This function is performance critical, as it can be run on every 352 // statement that generates an async event (like addEventListener) to support 353 // asynchronous call stacks. Thus, when possible, initialize the data lazily. 354 DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame, scopeDetailsLevel) 355 { 356 // Stuff that can not be initialized lazily (i.e. valid while paused with a valid break_id). 357 // The frameMirror and scopeMirror can be accessed only while paused on the debugger. 358 var frameDetails = frameMirror.details(); 359 360 var funcObject = frameDetails.func(); 361 var sourcePosition = frameDetails.sourcePosition(); 362 var thisObject = frameDetails.receiver(); 363 364 var isAtReturn = !!frameDetails.isAtReturn(); 365 var returnValue = isAtReturn ? frameDetails.returnValue() : undefined; 366 367 var scopeMirrors = (scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.NoScopes ? [] : frameMirror.allScopes(scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.FastAsyncScopes)); 368 var scopeTypes = new Array(scopeMirrors.length); 369 var scopeObjects = new Array(scopeMirrors.length); 370 for (var i = 0; i < scopeMirrors.length; ++i) { 371 var scopeDetails = scopeMirrors[i].details(); 372 scopeTypes[i] = scopeDetails.type(); 373 scopeObjects[i] = scopeDetails.object(); 374 } 375 376 // Calculated lazily. 377 var scopeChain; 378 var funcMirror; 379 var location; 380 381 function lazyScopeChain() 382 { 383 if (!scopeChain) { 384 scopeChain = []; 385 for (var i = 0; i < scopeObjects.length; ++i) 386 scopeChain.push(DebuggerScript._buildScopeObject(scopeTypes[i], scopeObjects[i])); 387 scopeObjects = null; // Free for GC. 388 } 389 return scopeChain; 390 } 391 392 function ensureFuncMirror() 393 { 394 if (!funcMirror) { 395 funcMirror = MakeMirror(funcObject); 396 if (!funcMirror.isFunction()) 397 funcMirror = new UnresolvedFunctionMirror(funcObject); 398 } 399 return funcMirror; 400 } 401 402 function ensureLocation() 403 { 404 if (!location) { 405 var script = ensureFuncMirror().script(); 406 if (script) 407 location = script.locationFromPosition(sourcePosition, true); 408 if (!location) 409 location = { line: 0, column: 0 }; 410 } 411 return location; 412 } 413 414 function line() 415 { 416 return ensureLocation().line; 417 } 418 419 function column() 420 { 421 return ensureLocation().column; 422 } 423 424 function sourceID() 425 { 426 var script = ensureFuncMirror().script(); 427 return script && script.id(); 428 } 429 430 function scriptName() 431 { 432 var script = ensureFuncMirror().script(); 433 return script && script.name(); 434 } 435 436 function functionName() 437 { 438 var func = ensureFuncMirror(); 439 if (!func.resolved()) 440 return undefined; 441 var displayName; 442 var valueMirror = func.property("displayName").value(); 443 if (valueMirror && valueMirror.isString()) 444 displayName = valueMirror.value(); 445 return displayName || func.name() || func.inferredName(); 446 } 447 448 function evaluate(expression) 449 { 450 return frameMirror.evaluate(expression, false).value(); 451 } 452 453 function restart() 454 { 455 return Debug.LiveEdit.RestartFrame(frameMirror); 456 } 457 458 function setVariableValue(scopeNumber, variableName, newValue) 459 { 460 return DebuggerScript._setScopeVariableValue(frameMirror, scopeNumber, variableName, newValue); 461 } 462 463 function stepInPositions() 464 { 465 var stepInPositionsV8 = frameMirror.stepInPositions(); 466 var stepInPositionsProtocol; 467 if (stepInPositionsV8) { 468 stepInPositionsProtocol = []; 469 var script = ensureFuncMirror().script(); 470 if (script) { 471 var scriptId = String(script.id()); 472 for (var i = 0; i < stepInPositionsV8.length; i++) { 473 var item = { 474 scriptId: scriptId, 475 lineNumber: stepInPositionsV8[i].position.line, 476 columnNumber: stepInPositionsV8[i].position.column 477 }; 478 stepInPositionsProtocol.push(item); 479 } 480 } 481 } 482 return JSON.stringify(stepInPositionsProtocol); 483 } 484 485 return { 486 "sourceID": sourceID, 487 "line": line, 488 "column": column, 489 "scriptName": scriptName, 490 "functionName": functionName, 491 "thisObject": thisObject, 492 "scopeChain": lazyScopeChain, 493 "scopeType": scopeTypes, 494 "evaluate": evaluate, 495 "caller": callerFrame, 496 "restart": restart, 497 "setVariableValue": setVariableValue, 498 "stepInPositions": stepInPositions, 499 "isAtReturn": isAtReturn, 500 "returnValue": returnValue 501 }; 502 } 503 504 DebuggerScript._buildScopeObject = function(scopeType, scopeObject) 505 { 506 var result; 507 switch (scopeType) { 508 case ScopeType.Local: 509 case ScopeType.Closure: 510 case ScopeType.Catch: 511 // For transient objects we create a "persistent" copy that contains 512 // the same properties. 513 // Reset scope object prototype to null so that the proto properties 514 // don't appear in the local scope section. 515 result = { __proto__: null }; 516 var properties = MakeMirror(scopeObject, true /* transient */).properties(); 517 for (var j = 0; j < properties.length; j++) { 518 var name = properties[j].name(); 519 if (name.charAt(0) === ".") 520 continue; // Skip internal variables like ".arguments" 521 result[name] = properties[j].value_; 522 } 523 break; 524 case ScopeType.Global: 525 case ScopeType.With: 526 result = scopeObject; 527 break; 528 case ScopeType.Block: 529 // Unsupported yet. Mustn't be reachable. 530 break; 531 } 532 return result; 533 } 534 535 DebuggerScript.getPromiseDetails = function(eventData) 536 { 537 return { 538 "promise": eventData.promise().value(), 539 "parentPromise": eventData.parentPromise().value(), 540 "status": eventData.status() 541 }; 542 } 543 544 // We never resolve Mirror by its handle so to avoid memory leaks caused by Mirrors in the cache we disable it. 545 ToggleMirrorCache(false); 546 547 return DebuggerScript; 548 })(); 549