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 33var DebuggerScript = {}; 34 35DebuggerScript.PauseOnExceptionsState = { 36 DontPauseOnExceptions: 0, 37 PauseOnAllExceptions: 1, 38 PauseOnUncaughtExceptions: 2 39}; 40 41DebuggerScript.ScopeInfoDetails = { 42 AllScopes: 0, 43 FastAsyncScopes: 1, 44 NoScopes: 2 45}; 46 47DebuggerScript._pauseOnExceptionsState = DebuggerScript.PauseOnExceptionsState.DontPauseOnExceptions; 48Debug.clearBreakOnException(); 49Debug.clearBreakOnUncaughtException(); 50 51DebuggerScript.getAfterCompileScript = function(eventData) 52{ 53 return DebuggerScript._formatScript(eventData.script_.script_); 54} 55 56DebuggerScript.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 71DebuggerScript.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 88DebuggerScript.getInternalProperties = function(value) 89{ 90 var properties = ObjectMirror.GetInternalProperties(value); 91 var result = []; 92 for (var i = 0; i < properties.length; i++) { 93 var mirror = properties[i]; 94 result.push({ 95 name: mirror.name(), 96 value: mirror.value().value() 97 }); 98 } 99 return result; 100} 101 102DebuggerScript.setFunctionVariableValue = function(functionValue, scopeIndex, variableName, newValue) 103{ 104 var mirror = MakeMirror(functionValue); 105 if (!mirror.isFunction()) 106 throw new Error("Function value has incorrect type"); 107 return DebuggerScript._setScopeVariableValue(mirror, scopeIndex, variableName, newValue); 108} 109 110DebuggerScript._setScopeVariableValue = function(scopeHolder, scopeIndex, variableName, newValue) 111{ 112 var scopeMirror = scopeHolder.scope(scopeIndex); 113 if (!scopeMirror) 114 throw new Error("Incorrect scope index"); 115 scopeMirror.setVariableValue(variableName, newValue); 116 return undefined; 117} 118 119DebuggerScript.getScripts = function(contextData) 120{ 121 var result = []; 122 123 if (!contextData) 124 return result; 125 var comma = contextData.indexOf(","); 126 if (comma === -1) 127 return result; 128 // Context data is a string in the following format: 129 // ("page"|"injected")","<page id> 130 var idSuffix = contextData.substring(comma); // including the comma 131 132 var scripts = Debug.scripts(); 133 for (var i = 0; i < scripts.length; ++i) { 134 var script = scripts[i]; 135 if (script.context_data && script.context_data.lastIndexOf(idSuffix) != -1) 136 result.push(DebuggerScript._formatScript(script)); 137 } 138 return result; 139} 140 141DebuggerScript._formatScript = function(script) 142{ 143 var lineEnds = script.line_ends; 144 var lineCount = lineEnds.length; 145 var endLine = script.line_offset + lineCount - 1; 146 var endColumn; 147 // V8 will not count last line if script source ends with \n. 148 if (script.source[script.source.length - 1] === '\n') { 149 endLine += 1; 150 endColumn = 0; 151 } else { 152 if (lineCount === 1) 153 endColumn = script.source.length + script.column_offset; 154 else 155 endColumn = script.source.length - (lineEnds[lineCount - 2] + 1); 156 } 157 158 return { 159 id: script.id, 160 name: script.nameOrSourceURL(), 161 source: script.source, 162 startLine: script.line_offset, 163 startColumn: script.column_offset, 164 endLine: endLine, 165 endColumn: endColumn, 166 isContentScript: !!script.context_data && script.context_data.indexOf("injected") == 0 167 }; 168} 169 170DebuggerScript.setBreakpoint = function(execState, info) 171{ 172 var positionAlignment = info.interstatementLocation ? Debug.BreakPositionAlignment.BreakPosition : Debug.BreakPositionAlignment.Statement; 173 var breakId = Debug.setScriptBreakPointById(info.sourceID, info.lineNumber, info.columnNumber, info.condition, undefined, positionAlignment); 174 175 var locations = Debug.findBreakPointActualLocations(breakId); 176 if (!locations.length) 177 return undefined; 178 info.lineNumber = locations[0].line; 179 info.columnNumber = locations[0].column; 180 return breakId.toString(); 181} 182 183DebuggerScript.removeBreakpoint = function(execState, info) 184{ 185 Debug.findBreakPoint(info.breakpointId, true); 186} 187 188DebuggerScript.pauseOnExceptionsState = function() 189{ 190 return DebuggerScript._pauseOnExceptionsState; 191} 192 193DebuggerScript.setPauseOnExceptionsState = function(newState) 194{ 195 DebuggerScript._pauseOnExceptionsState = newState; 196 197 if (DebuggerScript.PauseOnExceptionsState.PauseOnAllExceptions === newState) 198 Debug.setBreakOnException(); 199 else 200 Debug.clearBreakOnException(); 201 202 if (DebuggerScript.PauseOnExceptionsState.PauseOnUncaughtExceptions === newState) 203 Debug.setBreakOnUncaughtException(); 204 else 205 Debug.clearBreakOnUncaughtException(); 206} 207 208DebuggerScript.frameCount = function(execState) 209{ 210 return execState.frameCount(); 211} 212 213DebuggerScript.currentCallFrame = function(execState, data) 214{ 215 var maximumLimit = data >> 2; 216 var scopeDetailsLevel = data & 3; 217 218 var frameCount = execState.frameCount(); 219 if (maximumLimit && maximumLimit < frameCount) 220 frameCount = maximumLimit; 221 var topFrame = undefined; 222 for (var i = frameCount - 1; i >= 0; i--) { 223 var frameMirror = execState.frame(i); 224 topFrame = DebuggerScript._frameMirrorToJSCallFrame(frameMirror, topFrame, scopeDetailsLevel); 225 } 226 return topFrame; 227} 228 229DebuggerScript.stepIntoStatement = function(execState) 230{ 231 execState.prepareStep(Debug.StepAction.StepIn, 1); 232} 233 234DebuggerScript.stepOverStatement = function(execState, callFrame) 235{ 236 execState.prepareStep(Debug.StepAction.StepNext, 1); 237} 238 239DebuggerScript.stepOutOfFunction = function(execState, callFrame) 240{ 241 execState.prepareStep(Debug.StepAction.StepOut, 1); 242} 243 244// Returns array in form: 245// [ 0, <v8_result_report> ] in case of success 246// or [ 1, <general_error_message>, <compiler_message>, <line_number>, <column_number> ] in case of compile error, numbers are 1-based. 247// or throws exception with message. 248DebuggerScript.liveEditScriptSource = function(scriptId, newSource, preview) 249{ 250 var scripts = Debug.scripts(); 251 var scriptToEdit = null; 252 for (var i = 0; i < scripts.length; i++) { 253 if (scripts[i].id == scriptId) { 254 scriptToEdit = scripts[i]; 255 break; 256 } 257 } 258 if (!scriptToEdit) 259 throw("Script not found"); 260 261 var changeLog = []; 262 try { 263 var result = Debug.LiveEdit.SetScriptSource(scriptToEdit, newSource, preview, changeLog); 264 return [0, result]; 265 } catch (e) { 266 if (e instanceof Debug.LiveEdit.Failure && "details" in e) { 267 var details = e.details; 268 if (details.type === "liveedit_compile_error") { 269 var startPosition = details.position.start; 270 return [1, String(e), String(details.syntaxErrorMessage), Number(startPosition.line), Number(startPosition.column)]; 271 } 272 } 273 throw e; 274 } 275} 276 277DebuggerScript.clearBreakpoints = function(execState, info) 278{ 279 Debug.clearAllBreakPoints(); 280} 281 282DebuggerScript.setBreakpointsActivated = function(execState, info) 283{ 284 Debug.debuggerFlags().breakPointsActive.setValue(info.enabled); 285} 286 287DebuggerScript.getScriptSource = function(eventData) 288{ 289 return eventData.script().source(); 290} 291 292DebuggerScript.setScriptSource = function(eventData, source) 293{ 294 if (eventData.script().data() === "injected-script") 295 return; 296 eventData.script().setSource(source); 297} 298 299DebuggerScript.getScriptName = function(eventData) 300{ 301 return eventData.script().script_.nameOrSourceURL(); 302} 303 304DebuggerScript.getBreakpointNumbers = function(eventData) 305{ 306 var breakpoints = eventData.breakPointsHit(); 307 var numbers = []; 308 if (!breakpoints) 309 return numbers; 310 311 for (var i = 0; i < breakpoints.length; i++) { 312 var breakpoint = breakpoints[i]; 313 var scriptBreakPoint = breakpoint.script_break_point(); 314 numbers.push(scriptBreakPoint ? scriptBreakPoint.number() : breakpoint.number()); 315 } 316 return numbers; 317} 318 319DebuggerScript.isEvalCompilation = function(eventData) 320{ 321 var script = eventData.script(); 322 return (script.compilationType() === Debug.ScriptCompilationType.Eval); 323} 324 325// NOTE: This function is performance critical, as it can be run on every 326// statement that generates an async event (like addEventListener) to support 327// asynchronous call stacks. Thus, when possible, initialize the data lazily. 328DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame, scopeDetailsLevel) 329{ 330 // Stuff that can not be initialized lazily (i.e. valid while paused with a valid break_id). 331 // The frameMirror and scopeMirror can be accessed only while paused on the debugger. 332 var frameDetails = frameMirror.details(); 333 334 var funcObject = frameDetails.func(); 335 var sourcePosition = frameDetails.sourcePosition(); 336 var thisObject = frameDetails.receiver(); 337 338 var isAtReturn = !!frameDetails.isAtReturn(); 339 var returnValue = isAtReturn ? frameDetails.returnValue() : undefined; 340 341 var scopeMirrors = (scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.NoScopes ? [] : frameMirror.allScopes(scopeDetailsLevel === DebuggerScript.ScopeInfoDetails.FastAsyncScopes)); 342 var scopeTypes = new Array(scopeMirrors.length); 343 var scopeObjects = new Array(scopeMirrors.length); 344 for (var i = 0; i < scopeMirrors.length; ++i) { 345 var scopeDetails = scopeMirrors[i].details(); 346 scopeTypes[i] = scopeDetails.type(); 347 scopeObjects[i] = scopeDetails.object(); 348 } 349 350 // Calculated lazily. 351 var scopeChain; 352 var funcMirror; 353 var location; 354 355 function lazyScopeChain() 356 { 357 if (!scopeChain) { 358 scopeChain = []; 359 for (var i = 0; i < scopeObjects.length; ++i) 360 scopeChain.push(DebuggerScript._buildScopeObject(scopeTypes[i], scopeObjects[i])); 361 scopeObjects = null; // Free for GC. 362 } 363 return scopeChain; 364 } 365 366 function ensureFuncMirror() 367 { 368 if (!funcMirror) { 369 funcMirror = MakeMirror(funcObject); 370 if (!funcMirror.isFunction()) 371 funcMirror = new UnresolvedFunctionMirror(funcObject); 372 } 373 return funcMirror; 374 } 375 376 function ensureLocation() 377 { 378 if (!location) { 379 var script = ensureFuncMirror().script(); 380 if (script) 381 location = script.locationFromPosition(sourcePosition, true); 382 if (!location) 383 location = { line: 0, column: 0 }; 384 } 385 return location; 386 } 387 388 function line() 389 { 390 return ensureLocation().line; 391 } 392 393 function column() 394 { 395 return ensureLocation().column; 396 } 397 398 function sourceID() 399 { 400 var script = ensureFuncMirror().script(); 401 return script && script.id(); 402 } 403 404 function functionName() 405 { 406 var func = ensureFuncMirror(); 407 if (!func.resolved()) 408 return undefined; 409 var displayName; 410 var valueMirror = func.property("displayName").value(); 411 if (valueMirror && valueMirror.isString()) 412 displayName = valueMirror.value(); 413 return displayName || func.name() || func.inferredName(); 414 } 415 416 function evaluate(expression) 417 { 418 return frameMirror.evaluate(expression, false).value(); 419 } 420 421 function restart() 422 { 423 return Debug.LiveEdit.RestartFrame(frameMirror); 424 } 425 426 function setVariableValue(scopeNumber, variableName, newValue) 427 { 428 return DebuggerScript._setScopeVariableValue(frameMirror, scopeNumber, variableName, newValue); 429 } 430 431 function stepInPositions() 432 { 433 var stepInPositionsV8 = frameMirror.stepInPositions(); 434 var stepInPositionsProtocol; 435 if (stepInPositionsV8) { 436 stepInPositionsProtocol = []; 437 var script = ensureFuncMirror().script(); 438 if (script) { 439 var scriptId = String(script.id()); 440 for (var i = 0; i < stepInPositionsV8.length; i++) { 441 var item = { 442 scriptId: scriptId, 443 lineNumber: stepInPositionsV8[i].position.line, 444 columnNumber: stepInPositionsV8[i].position.column 445 }; 446 stepInPositionsProtocol.push(item); 447 } 448 } 449 } 450 return JSON.stringify(stepInPositionsProtocol); 451 } 452 453 return { 454 "sourceID": sourceID, 455 "line": line, 456 "column": column, 457 "functionName": functionName, 458 "thisObject": thisObject, 459 "scopeChain": lazyScopeChain, 460 "scopeType": scopeTypes, 461 "evaluate": evaluate, 462 "caller": callerFrame, 463 "restart": restart, 464 "setVariableValue": setVariableValue, 465 "stepInPositions": stepInPositions, 466 "isAtReturn": isAtReturn, 467 "returnValue": returnValue 468 }; 469} 470 471DebuggerScript._buildScopeObject = function(scopeType, scopeObject) 472{ 473 var result; 474 switch (scopeType) { 475 case ScopeType.Local: 476 case ScopeType.Closure: 477 case ScopeType.Catch: 478 // For transient objects we create a "persistent" copy that contains 479 // the same properties. 480 // Reset scope object prototype to null so that the proto properties 481 // don't appear in the local scope section. 482 result = { __proto__: null }; 483 var properties = MakeMirror(scopeObject, true /* transient */).properties(); 484 for (var j = 0; j < properties.length; j++) { 485 var name = properties[j].name(); 486 if (name.charAt(0) === ".") 487 continue; // Skip internal variables like ".arguments" 488 result[name] = properties[j].value_; 489 } 490 break; 491 case ScopeType.Global: 492 case ScopeType.With: 493 result = scopeObject; 494 break; 495 case ScopeType.Block: 496 // Unsupported yet. Mustn't be reachable. 497 break; 498 } 499 return result; 500} 501 502// We never resolve Mirror by its handle so to avoid memory leaks caused by Mirrors in the cache we disable it. 503ToggleMirrorCache(false); 504 505return DebuggerScript; 506})(); 507