1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * @constructor 7 * @implements {WebInspector.TargetManager.Observer} 8 * @param {!WebInspector.TargetManager} targetManager 9 * @param {!WebInspector.Workspace} workspace 10 * @param {!WebInspector.NetworkWorkspaceBinding} networkWorkspaceBinding 11 */ 12 WebInspector.DebuggerWorkspaceBinding = function(targetManager, workspace, networkWorkspaceBinding) 13 { 14 this._workspace = workspace; 15 this._networkWorkspaceBinding = networkWorkspaceBinding; 16 17 /** @type {!Map.<!WebInspector.Target, !WebInspector.DebuggerWorkspaceBinding.TargetData>} */ 18 this._targetToData = new Map(); 19 targetManager.observeTargets(this); 20 21 targetManager.addModelListener(WebInspector.DebuggerModel, WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._globalObjectCleared, this); 22 targetManager.addModelListener(WebInspector.DebuggerModel, WebInspector.DebuggerModel.Events.DebuggerResumed, this._debuggerResumed, this); 23 workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this); 24 workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved, this); 25 } 26 27 WebInspector.DebuggerWorkspaceBinding.prototype = { 28 /** 29 * @param {!WebInspector.Target} target 30 */ 31 targetAdded: function(target) 32 { 33 this._targetToData.set(target, new WebInspector.DebuggerWorkspaceBinding.TargetData(target, this)); 34 }, 35 36 /** 37 * @param {!WebInspector.Target} target 38 */ 39 targetRemoved: function(target) 40 { 41 var targetData = this._targetToData.get(target); 42 targetData._dispose(); 43 this._targetToData.remove(target); 44 }, 45 46 /** 47 * @param {!WebInspector.Event} event 48 */ 49 _uiSourceCodeRemoved: function(event) 50 { 51 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); 52 var targetDatas = this._targetToData.values(); 53 for (var i = 0; i < targetDatas.length; ++i) 54 targetDatas[i]._uiSourceCodeRemoved(uiSourceCode); 55 }, 56 57 /** 58 * @param {!WebInspector.Event} event 59 */ 60 _projectRemoved: function(event) 61 { 62 var project = /** @type {!WebInspector.Project} */ (event.data); 63 var targetDatas = this._targetToData.values(); 64 var uiSourceCodes = project.uiSourceCodes(); 65 for (var i = 0; i < targetDatas.length; ++i) { 66 for (var j = 0; j < uiSourceCodes.length; ++j) 67 targetDatas[i]._uiSourceCodeRemoved(uiSourceCodes[j]); 68 } 69 }, 70 71 /** 72 * @param {!WebInspector.Script} script 73 * @param {!WebInspector.DebuggerSourceMapping} sourceMapping 74 */ 75 pushSourceMapping: function(script, sourceMapping) 76 { 77 var info = this._ensureInfoForScript(script); 78 info._pushSourceMapping(sourceMapping); 79 }, 80 81 /** 82 * @param {!WebInspector.Script} script 83 * @return {!WebInspector.DebuggerSourceMapping} 84 */ 85 popSourceMapping: function(script) 86 { 87 var info = this._infoForScript(script.target(), script.scriptId); 88 console.assert(info); 89 return info._popSourceMapping(); 90 }, 91 92 /** 93 * @param {!WebInspector.Target} target 94 * @param {!WebInspector.UISourceCode} uiSourceCode 95 * @param {?WebInspector.DebuggerSourceMapping} sourceMapping 96 */ 97 setSourceMapping: function(target, uiSourceCode, sourceMapping) 98 { 99 var data = this._targetToData.get(target); 100 if (data) 101 data._setSourceMapping(uiSourceCode, sourceMapping); 102 }, 103 104 /** 105 * @param {!WebInspector.Script} script 106 */ 107 updateLocations: function(script) 108 { 109 var info = this._infoForScript(script.target(), script.scriptId); 110 if (info) 111 info._updateLocations(); 112 }, 113 114 /** 115 * @param {!WebInspector.DebuggerModel.Location} rawLocation 116 * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate 117 * @return {!WebInspector.DebuggerWorkspaceBinding.Location} 118 */ 119 createLiveLocation: function(rawLocation, updateDelegate) 120 { 121 var info = this._infoForScript(rawLocation.target(), rawLocation.scriptId); 122 console.assert(info); 123 var location = new WebInspector.DebuggerWorkspaceBinding.Location(info._script, rawLocation, this, updateDelegate); 124 info._addLocation(location); 125 return location; 126 }, 127 128 /** 129 * @param {!WebInspector.DebuggerModel.CallFrame} callFrame 130 * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate 131 * @return {!WebInspector.DebuggerWorkspaceBinding.Location} 132 */ 133 createCallFrameLiveLocation: function(callFrame, updateDelegate) 134 { 135 var target = callFrame.target(); 136 this._ensureInfoForScript(callFrame.script) 137 var location = this.createLiveLocation(callFrame.location(), updateDelegate); 138 this._registerCallFrameLiveLocation(target, location); 139 return location; 140 }, 141 142 /** 143 * @param {!WebInspector.DebuggerModel.Location} rawLocation 144 * @return {!WebInspector.UILocation} 145 */ 146 rawLocationToUILocation: function(rawLocation) 147 { 148 var info = this._infoForScript(rawLocation.target(), rawLocation.scriptId); 149 console.assert(info); 150 return info._rawLocationToUILocation(rawLocation); 151 }, 152 153 /** 154 * @param {!WebInspector.Target} target 155 * @param {!WebInspector.UISourceCode} uiSourceCode 156 * @param {number} lineNumber 157 * @param {number} columnNumber 158 * @return {?WebInspector.DebuggerModel.Location} 159 */ 160 uiLocationToRawLocation: function(target, uiSourceCode, lineNumber, columnNumber) 161 { 162 var targetData = this._targetToData.get(target); 163 return targetData ? /** @type {?WebInspector.DebuggerModel.Location} */ (targetData._uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber)) : null; 164 }, 165 166 /** 167 * @param {!WebInspector.UISourceCode} uiSourceCode 168 * @param {number} lineNumber 169 * @param {number} columnNumber 170 * @return {!Array.<!WebInspector.DebuggerModel.Location>} 171 */ 172 uiLocationToRawLocations: function(uiSourceCode, lineNumber, columnNumber) 173 { 174 var result = []; 175 var targetDatas = this._targetToData.values(); 176 for (var i = 0; i < targetDatas.length; ++i) { 177 var rawLocation = targetDatas[i]._uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber); 178 if (rawLocation) 179 result.push(rawLocation); 180 } 181 return result; 182 }, 183 184 /** 185 * @param {!WebInspector.UISourceCode} uiSourceCode 186 * @param {number} lineNumber 187 * @return {boolean} 188 */ 189 uiLineHasMapping: function(uiSourceCode, lineNumber) 190 { 191 var targetDatas = this._targetToData.values(); 192 for (var i = 0; i < targetDatas.length; ++i) { 193 if (!targetDatas[i]._uiLineHasMapping(uiSourceCode, lineNumber)) 194 return false; 195 } 196 return true; 197 }, 198 199 /** 200 * @param {!WebInspector.Target} target 201 * @return {?WebInspector.LiveEditSupport} 202 */ 203 liveEditSupport: function(target) 204 { 205 var targetData = this._targetToData.get(target); 206 return targetData ? targetData._liveEditSupport : null; 207 }, 208 209 /** 210 * @param {!WebInspector.UISourceCode} uiSourceCode 211 * @param {!WebInspector.Target} target 212 * @return {?WebInspector.ResourceScriptFile} 213 */ 214 scriptFile: function(uiSourceCode, target) 215 { 216 var targetData = this._targetToData.get(target); 217 return targetData ? targetData._resourceMapping.scriptFile(uiSourceCode) : null; 218 }, 219 220 /** 221 * @param {!WebInspector.Event} event 222 */ 223 _globalObjectCleared: function(event) 224 { 225 var debuggerModel = /** @type {!WebInspector.DebuggerModel} */ (event.target); 226 this._reset(debuggerModel.target()); 227 }, 228 229 /** 230 * @param {!WebInspector.Target} target 231 */ 232 _reset: function(target) 233 { 234 var targetData = this._targetToData.get(target); 235 targetData.callFrameLocations.values().forEach(function(location) { location.dispose(); }); 236 targetData.callFrameLocations.clear(); 237 }, 238 239 /** 240 * @param {!WebInspector.Script} script 241 * @return {!WebInspector.DebuggerWorkspaceBinding.ScriptInfo} 242 */ 243 _ensureInfoForScript: function(script) 244 { 245 var scriptDataMap = this._targetToData.get(script.target()).scriptDataMap; 246 var info = scriptDataMap.get(script.scriptId); 247 if (!info) { 248 info = new WebInspector.DebuggerWorkspaceBinding.ScriptInfo(script); 249 scriptDataMap.set(script.scriptId, info); 250 } 251 return info; 252 }, 253 254 255 /** 256 * @param {!WebInspector.Target} target 257 * @param {string} scriptId 258 * @return {?WebInspector.DebuggerWorkspaceBinding.ScriptInfo} 259 */ 260 _infoForScript: function(target, scriptId) 261 { 262 var data = this._targetToData.get(target); 263 if (!data) 264 return null; 265 return data.scriptDataMap.get(scriptId) || null; 266 }, 267 268 /** 269 * @param {!WebInspector.Target} target 270 * @param {!WebInspector.DebuggerWorkspaceBinding.Location} location 271 */ 272 _registerCallFrameLiveLocation: function(target, location) 273 { 274 var locations = this._targetToData.get(target).callFrameLocations; 275 locations.add(location); 276 }, 277 278 /** 279 * @param {!WebInspector.DebuggerWorkspaceBinding.Location} location 280 */ 281 _removeLiveLocation: function(location) 282 { 283 var info = this._infoForScript(location._script.target(), location._script.scriptId); 284 if (info) 285 info._removeLocation(location); 286 }, 287 288 /** 289 * @param {!WebInspector.Event} event 290 */ 291 _debuggerResumed: function(event) 292 { 293 var debuggerModel = /** @type {!WebInspector.DebuggerModel} */ (event.target); 294 this._reset(debuggerModel.target()); 295 } 296 } 297 298 /** 299 * @constructor 300 * @param {!WebInspector.Target} target 301 * @param {!WebInspector.DebuggerWorkspaceBinding} debuggerWorkspaceBinding 302 */ 303 WebInspector.DebuggerWorkspaceBinding.TargetData = function(target, debuggerWorkspaceBinding) 304 { 305 this._target = target; 306 307 /** @type {!StringMap.<!WebInspector.DebuggerWorkspaceBinding.ScriptInfo>} */ 308 this.scriptDataMap = new StringMap(); 309 310 /** @type {!Set.<!WebInspector.DebuggerWorkspaceBinding.Location>} */ 311 this.callFrameLocations = new Set(); 312 313 var debuggerModel = target.debuggerModel; 314 var workspace = debuggerWorkspaceBinding._workspace; 315 316 this._liveEditSupport = new WebInspector.LiveEditSupport(target, workspace, debuggerWorkspaceBinding); 317 this._defaultMapping = new WebInspector.DefaultScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding); 318 this._resourceMapping = new WebInspector.ResourceScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding); 319 this._compilerMapping = new WebInspector.CompilerScriptMapping(debuggerModel, workspace, debuggerWorkspaceBinding._networkWorkspaceBinding, debuggerWorkspaceBinding); 320 321 /** @type {!WebInspector.LiveEditSupport} */ 322 this._liveEditSupport = new WebInspector.LiveEditSupport(target, workspace, debuggerWorkspaceBinding); 323 324 /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.DebuggerSourceMapping>} */ 325 this._uiSourceCodeToSourceMapping = new Map(); 326 327 debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.ParsedScriptSource, this._parsedScriptSource, this); 328 debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, this._parsedScriptSource, this); 329 } 330 331 WebInspector.DebuggerWorkspaceBinding.TargetData.prototype = { 332 /** 333 * @param {!WebInspector.Event} event 334 */ 335 _parsedScriptSource: function(event) 336 { 337 var script = /** @type {!WebInspector.Script} */ (event.data); 338 this._defaultMapping.addScript(script); 339 340 if (script.isSnippet()) { 341 WebInspector.scriptSnippetModel.addScript(script); 342 return; 343 } 344 345 this._resourceMapping.addScript(script); 346 347 if (WebInspector.settings.jsSourceMapsEnabled.get()) 348 this._compilerMapping.addScript(script); 349 }, 350 351 /** 352 * @param {!WebInspector.UISourceCode} uiSourceCode 353 * @param {?WebInspector.DebuggerSourceMapping} sourceMapping 354 */ 355 _setSourceMapping: function(uiSourceCode, sourceMapping) 356 { 357 if (this._uiSourceCodeToSourceMapping.get(uiSourceCode) === sourceMapping) 358 return; 359 360 if (sourceMapping) 361 this._uiSourceCodeToSourceMapping.set(uiSourceCode, sourceMapping); 362 else 363 this._uiSourceCodeToSourceMapping.remove(uiSourceCode); 364 365 uiSourceCode.dispatchEventToListeners(WebInspector.UISourceCode.Events.SourceMappingChanged, {target: this._target, isIdentity: sourceMapping ? sourceMapping.isIdentity() : false}); 366 }, 367 368 /** 369 * @param {!WebInspector.UISourceCode} uiSourceCode 370 * @param {number} lineNumber 371 * @param {number} columnNumber 372 * @return {?WebInspector.DebuggerModel.Location} 373 */ 374 _uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) 375 { 376 var sourceMapping = this._uiSourceCodeToSourceMapping.get(uiSourceCode); 377 return sourceMapping ? sourceMapping.uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber) : null; 378 }, 379 380 /** 381 * @param {!WebInspector.UISourceCode} uiSourceCode 382 * @param {number} lineNumber 383 * @return {boolean} 384 */ 385 _uiLineHasMapping: function(uiSourceCode, lineNumber) 386 { 387 var sourceMapping = this._uiSourceCodeToSourceMapping.get(uiSourceCode); 388 return sourceMapping ? sourceMapping.uiLineHasMapping(uiSourceCode, lineNumber) : true; 389 }, 390 391 /** 392 * @param {!WebInspector.UISourceCode} uiSourceCode 393 */ 394 _uiSourceCodeRemoved: function(uiSourceCode) 395 { 396 this._uiSourceCodeToSourceMapping.remove(uiSourceCode); 397 }, 398 399 _dispose: function() 400 { 401 this._compilerMapping.dispose(); 402 this._resourceMapping.dispose(); 403 this._defaultMapping.dispose(); 404 this._uiSourceCodeToSourceMapping.clear(); 405 } 406 } 407 408 /** 409 * @constructor 410 * @param {!WebInspector.Script} script 411 */ 412 WebInspector.DebuggerWorkspaceBinding.ScriptInfo = function(script) 413 { 414 this._script = script; 415 416 /** @type {!Array.<!WebInspector.DebuggerSourceMapping>} */ 417 this._sourceMappings = []; 418 419 /** @type {!Set.<!WebInspector.LiveLocation>} */ 420 this._locations = new Set(); 421 } 422 423 WebInspector.DebuggerWorkspaceBinding.ScriptInfo.prototype = { 424 /** 425 * @param {!WebInspector.DebuggerSourceMapping} sourceMapping 426 */ 427 _pushSourceMapping: function(sourceMapping) 428 { 429 this._sourceMappings.push(sourceMapping); 430 this._updateLocations(); 431 }, 432 433 /** 434 * @return {!WebInspector.DebuggerSourceMapping} 435 */ 436 _popSourceMapping: function() 437 { 438 var sourceMapping = this._sourceMappings.pop(); 439 this._updateLocations(); 440 return sourceMapping; 441 }, 442 443 /** 444 * @param {!WebInspector.LiveLocation} location 445 */ 446 _addLocation: function(location) 447 { 448 this._locations.add(location); 449 location.update(); 450 }, 451 452 /** 453 * @param {!WebInspector.LiveLocation} location 454 */ 455 _removeLocation: function(location) 456 { 457 this._locations.remove(location); 458 }, 459 460 _updateLocations: function() 461 { 462 var items = this._locations.values(); 463 for (var i = 0; i < items.length; ++i) 464 items[i].update(); 465 }, 466 467 /** 468 * @param {!WebInspector.DebuggerModel.Location} rawLocation 469 * @return {!WebInspector.UILocation} 470 */ 471 _rawLocationToUILocation: function(rawLocation) 472 { 473 var uiLocation; 474 for (var i = this._sourceMappings.length - 1; !uiLocation && i >= 0; --i) 475 uiLocation = this._sourceMappings[i].rawLocationToUILocation(rawLocation); 476 console.assert(uiLocation, "Script raw location cannot be mapped to any UI location."); 477 return /** @type {!WebInspector.UILocation} */ (uiLocation); 478 } 479 } 480 481 482 /** 483 * @constructor 484 * @extends {WebInspector.LiveLocation} 485 * @param {!WebInspector.Script} script 486 * @param {!WebInspector.DebuggerModel.Location} rawLocation 487 * @param {!WebInspector.DebuggerWorkspaceBinding} binding 488 * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate 489 */ 490 WebInspector.DebuggerWorkspaceBinding.Location = function(script, rawLocation, binding, updateDelegate) 491 { 492 WebInspector.LiveLocation.call(this, updateDelegate); 493 this._script = script; 494 this._rawLocation = rawLocation; 495 this._binding = binding; 496 } 497 498 WebInspector.DebuggerWorkspaceBinding.Location.prototype = { 499 /** 500 * @return {!WebInspector.UILocation} 501 */ 502 uiLocation: function() 503 { 504 var debuggerModelLocation = this._rawLocation; 505 return this._binding.rawLocationToUILocation(debuggerModelLocation); 506 }, 507 508 dispose: function() 509 { 510 WebInspector.LiveLocation.prototype.dispose.call(this); 511 this._binding._removeLiveLocation(this); 512 }, 513 514 __proto__: WebInspector.LiveLocation.prototype 515 } 516 517 /** 518 * @interface 519 */ 520 WebInspector.DebuggerSourceMapping = function() 521 { 522 } 523 524 WebInspector.DebuggerSourceMapping.prototype = { 525 /** 526 * @param {!WebInspector.DebuggerModel.Location} rawLocation 527 * @return {?WebInspector.UILocation} 528 */ 529 rawLocationToUILocation: function(rawLocation) { }, 530 531 /** 532 * @param {!WebInspector.UISourceCode} uiSourceCode 533 * @param {number} lineNumber 534 * @param {number} columnNumber 535 * @return {?WebInspector.DebuggerModel.Location} 536 */ 537 uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) { }, 538 539 /** 540 * @return {boolean} 541 */ 542 isIdentity: function() { }, 543 544 /** 545 * @param {!WebInspector.UISourceCode} uiSourceCode 546 * @param {number} lineNumber 547 * @return {boolean} 548 */ 549 uiLineHasMapping: function(uiSourceCode, lineNumber) { } 550 } 551 552 /** 553 * @type {!WebInspector.DebuggerWorkspaceBinding} 554 */ 555 WebInspector.debuggerWorkspaceBinding; 556