1 /* 2 * Copyright (C) 2012 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 /** 32 * @constructor 33 * @implements {WebInspector.DebuggerSourceMapping} 34 * @param {!WebInspector.DebuggerModel} debuggerModel 35 * @param {!WebInspector.Workspace} workspace 36 * @param {!WebInspector.DebuggerWorkspaceBinding} debuggerWorkspaceBinding 37 */ 38 WebInspector.ResourceScriptMapping = function(debuggerModel, workspace, debuggerWorkspaceBinding) 39 { 40 this._target = debuggerModel.target(); 41 this._debuggerModel = debuggerModel; 42 this._workspace = workspace; 43 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this); 44 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this); 45 this._debuggerWorkspaceBinding = debuggerWorkspaceBinding; 46 this._boundURLs = new StringSet(); 47 48 /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.ResourceScriptFile>} */ 49 this._uiSourceCodeToScriptFile = new Map(); 50 51 debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this); 52 } 53 54 WebInspector.ResourceScriptMapping.prototype = { 55 /** 56 * @param {!WebInspector.DebuggerModel.Location} rawLocation 57 * @return {?WebInspector.UILocation} 58 */ 59 rawLocationToUILocation: function(rawLocation) 60 { 61 var debuggerModelLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (rawLocation); 62 var script = debuggerModelLocation.script(); 63 var uiSourceCode = this._workspaceUISourceCodeForScript(script); 64 if (!uiSourceCode) 65 return null; 66 var scriptFile = this.scriptFile(uiSourceCode); 67 if (scriptFile && ((scriptFile.hasDivergedFromVM() && !scriptFile.isMergingToVM()) || scriptFile.isDivergingFromVM())) 68 return null; 69 var lineNumber = debuggerModelLocation.lineNumber - (script.isInlineScriptWithSourceURL() ? script.lineOffset : 0); 70 var columnNumber = debuggerModelLocation.columnNumber || 0; 71 if (script.isInlineScriptWithSourceURL() && !lineNumber && columnNumber) 72 columnNumber -= script.columnOffset; 73 return uiSourceCode.uiLocation(debuggerModelLocation.lineNumber, debuggerModelLocation.columnNumber || 0); 74 }, 75 76 /** 77 * @param {!WebInspector.UISourceCode} uiSourceCode 78 * @param {number} lineNumber 79 * @param {number} columnNumber 80 * @return {?WebInspector.DebuggerModel.Location} 81 */ 82 uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) 83 { 84 var scripts = this._scriptsForUISourceCode(uiSourceCode); 85 console.assert(scripts.length); 86 var script = scripts[0]; 87 if (script.isInlineScriptWithSourceURL()) 88 return this._debuggerModel.createRawLocation(script, lineNumber + script.lineOffset, lineNumber ? columnNumber : columnNumber + script.columnOffset); 89 return this._debuggerModel.createRawLocation(script, lineNumber, columnNumber); 90 }, 91 92 /** 93 * @param {!WebInspector.Script} script 94 */ 95 addScript: function(script) 96 { 97 if (script.isAnonymousScript()) 98 return; 99 this._debuggerWorkspaceBinding.pushSourceMapping(script, this); 100 101 var uiSourceCode = this._workspaceUISourceCodeForScript(script); 102 if (!uiSourceCode) 103 return; 104 105 this._bindUISourceCodeToScripts(uiSourceCode, [script]); 106 }, 107 108 /** 109 * @return {boolean} 110 */ 111 isIdentity: function() 112 { 113 return true; 114 }, 115 116 /** 117 * @param {!WebInspector.UISourceCode} uiSourceCode 118 * @param {number} lineNumber 119 * @return {boolean} 120 */ 121 uiLineHasMapping: function(uiSourceCode, lineNumber) 122 { 123 return true; 124 }, 125 126 /** 127 * @param {!WebInspector.UISourceCode} uiSourceCode 128 * @return {?WebInspector.ResourceScriptFile} 129 */ 130 scriptFile: function(uiSourceCode) 131 { 132 return this._uiSourceCodeToScriptFile.get(uiSourceCode) || null; 133 }, 134 135 /** 136 * @param {!WebInspector.UISourceCode} uiSourceCode 137 * @param {?WebInspector.ResourceScriptFile} scriptFile 138 */ 139 _setScriptFile: function(uiSourceCode, scriptFile) 140 { 141 if (scriptFile) 142 this._uiSourceCodeToScriptFile.set(uiSourceCode, scriptFile); 143 else 144 this._uiSourceCodeToScriptFile.remove(uiSourceCode); 145 }, 146 147 /** 148 * @param {!WebInspector.Event} event 149 */ 150 _uiSourceCodeAdded: function(event) 151 { 152 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); 153 if (!uiSourceCode.url) 154 return; 155 if (uiSourceCode.project().isServiceProject()) 156 return; 157 158 var scripts = this._scriptsForUISourceCode(uiSourceCode); 159 if (!scripts.length) 160 return; 161 162 this._bindUISourceCodeToScripts(uiSourceCode, scripts); 163 }, 164 165 /** 166 * @param {!WebInspector.Event} event 167 */ 168 _uiSourceCodeRemoved: function(event) 169 { 170 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); 171 if (!uiSourceCode.url) 172 return; 173 if (uiSourceCode.project().isServiceProject()) 174 return; 175 176 this._unbindUISourceCode(uiSourceCode); 177 }, 178 179 /** 180 * @param {!WebInspector.UISourceCode} uiSourceCode 181 */ 182 _hasMergedToVM: function(uiSourceCode) 183 { 184 var scripts = this._scriptsForUISourceCode(uiSourceCode); 185 if (!scripts.length) 186 return; 187 for (var i = 0; i < scripts.length; ++i) 188 this._debuggerWorkspaceBinding.updateLocations(scripts[i]); 189 }, 190 191 /** 192 * @param {!WebInspector.UISourceCode} uiSourceCode 193 */ 194 _hasDivergedFromVM: function(uiSourceCode) 195 { 196 var scripts = this._scriptsForUISourceCode(uiSourceCode); 197 if (!scripts.length) 198 return; 199 for (var i = 0; i < scripts.length; ++i) 200 this._debuggerWorkspaceBinding.updateLocations(scripts[i]); 201 }, 202 203 /** 204 * @param {!WebInspector.Script} script 205 * @return {?WebInspector.UISourceCode} 206 */ 207 _workspaceUISourceCodeForScript: function(script) 208 { 209 if (script.isAnonymousScript()) 210 return null; 211 return this._workspace.uiSourceCodeForURL(script.sourceURL); 212 }, 213 214 /** 215 * @param {!WebInspector.UISourceCode} uiSourceCode 216 * @return {!Array.<!WebInspector.Script>} 217 */ 218 _scriptsForUISourceCode: function(uiSourceCode) 219 { 220 if (!uiSourceCode.url) 221 return []; 222 return this._debuggerModel.scriptsForSourceURL(uiSourceCode.url); 223 }, 224 225 /** 226 * @param {!WebInspector.UISourceCode} uiSourceCode 227 * @param {!Array.<!WebInspector.Script>} scripts 228 */ 229 _bindUISourceCodeToScripts: function(uiSourceCode, scripts) 230 { 231 console.assert(scripts.length); 232 var scriptFile = new WebInspector.ResourceScriptFile(this, uiSourceCode, scripts); 233 this._setScriptFile(uiSourceCode, scriptFile); 234 for (var i = 0; i < scripts.length; ++i) 235 this._debuggerWorkspaceBinding.updateLocations(scripts[i]); 236 this._debuggerWorkspaceBinding.setSourceMapping(this._target, uiSourceCode, this); 237 this._boundURLs.add(uiSourceCode.url); 238 }, 239 240 /** 241 * @param {!WebInspector.UISourceCode} uiSourceCode 242 */ 243 _unbindUISourceCode: function(uiSourceCode) 244 { 245 var scriptFile = this.scriptFile(uiSourceCode); 246 if (scriptFile) { 247 scriptFile.dispose(); 248 this._setScriptFile(uiSourceCode, null); 249 } 250 this._debuggerWorkspaceBinding.setSourceMapping(this._target, uiSourceCode, null); 251 }, 252 253 _debuggerReset: function() 254 { 255 var boundURLs = this._boundURLs.values(); 256 for (var i = 0; i < boundURLs.length; ++i) 257 { 258 var uiSourceCode = this._workspace.uiSourceCodeForURL(boundURLs[i]); 259 if (!uiSourceCode) 260 continue; 261 this._unbindUISourceCode(uiSourceCode); 262 } 263 this._boundURLs.clear(); 264 }, 265 266 dispose: function() 267 { 268 this._debuggerReset(); 269 this._workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this); 270 this._workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this); 271 } 272 273 } 274 275 /** 276 * @constructor 277 * @extends {WebInspector.Object} 278 * @param {!WebInspector.ResourceScriptMapping} resourceScriptMapping 279 * @param {!WebInspector.UISourceCode} uiSourceCode 280 * @param {!Array.<!WebInspector.Script>} scripts 281 */ 282 WebInspector.ResourceScriptFile = function(resourceScriptMapping, uiSourceCode, scripts) 283 { 284 console.assert(scripts.length); 285 286 this._resourceScriptMapping = resourceScriptMapping; 287 this._uiSourceCode = uiSourceCode; 288 289 if (this._uiSourceCode.contentType() === WebInspector.resourceTypes.Script) 290 this._script = scripts[0]; 291 292 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 293 this._update(); 294 } 295 296 WebInspector.ResourceScriptFile.Events = { 297 DidMergeToVM: "DidMergeToVM", 298 DidDivergeFromVM: "DidDivergeFromVM", 299 } 300 301 WebInspector.ResourceScriptFile.prototype = { 302 /** 303 * @param {function(?string,!DebuggerAgent.SetScriptSourceError=,!WebInspector.Script=)=} callback 304 */ 305 commitLiveEdit: function(callback) 306 { 307 var target = this._resourceScriptMapping._target; 308 /** 309 * @param {?string} error 310 * @param {!DebuggerAgent.SetScriptSourceError=} errorData 311 * @this {WebInspector.ResourceScriptFile} 312 */ 313 function innerCallback(error, errorData) 314 { 315 if (!error) 316 this._scriptSource = source; 317 this._update(); 318 if (callback) 319 callback(error, errorData, this._script); 320 } 321 if (!this._script) 322 return; 323 var source = this._uiSourceCode.workingCopy(); 324 target.debuggerModel.setScriptSource(this._script.scriptId, source, innerCallback.bind(this)); 325 }, 326 327 /** 328 * @return {boolean} 329 */ 330 _isDiverged: function() 331 { 332 if (this._uiSourceCode.isDirty()) 333 return true; 334 if (!this._script) 335 return false; 336 if (typeof this._scriptSource === "undefined") 337 return false; 338 if (!this._uiSourceCode.workingCopy().startsWith(this._scriptSource)) 339 return true; 340 var suffix = this._uiSourceCode.workingCopy().substr(this._scriptSource.length); 341 return !!suffix.length && !suffix.match(WebInspector.Script.sourceURLRegex); 342 }, 343 344 /** 345 * @param {!WebInspector.Event} event 346 */ 347 _workingCopyChanged: function(event) 348 { 349 this._update(); 350 }, 351 352 _update: function() 353 { 354 if (this._isDiverged() && !this._hasDivergedFromVM) 355 this._divergeFromVM(); 356 else if (!this._isDiverged() && this._hasDivergedFromVM) 357 this._mergeToVM(); 358 }, 359 360 _divergeFromVM: function() 361 { 362 this._isDivergingFromVM = true; 363 this._resourceScriptMapping._hasDivergedFromVM(this._uiSourceCode); 364 delete this._isDivergingFromVM; 365 this._hasDivergedFromVM = true; 366 this.dispatchEventToListeners(WebInspector.ResourceScriptFile.Events.DidDivergeFromVM, this._uiSourceCode); 367 }, 368 369 _mergeToVM: function() 370 { 371 delete this._hasDivergedFromVM; 372 this._isMergingToVM = true; 373 this._resourceScriptMapping._hasMergedToVM(this._uiSourceCode); 374 delete this._isMergingToVM; 375 this.dispatchEventToListeners(WebInspector.ResourceScriptFile.Events.DidMergeToVM, this._uiSourceCode); 376 }, 377 378 /** 379 * @return {boolean} 380 */ 381 hasDivergedFromVM: function() 382 { 383 return this._hasDivergedFromVM; 384 }, 385 386 /** 387 * @return {boolean} 388 */ 389 isDivergingFromVM: function() 390 { 391 return this._isDivergingFromVM; 392 }, 393 394 /** 395 * @return {boolean} 396 */ 397 isMergingToVM: function() 398 { 399 return this._isMergingToVM; 400 }, 401 402 checkMapping: function() 403 { 404 if (!this._script) 405 return; 406 if (typeof this._scriptSource !== "undefined") 407 return; 408 this._script.requestContent(callback.bind(this)); 409 410 /** 411 * @param {?string} source 412 * @this {WebInspector.ResourceScriptFile} 413 */ 414 function callback(source) 415 { 416 this._scriptSource = source; 417 this._update(); 418 } 419 }, 420 421 /** 422 * @return {?WebInspector.Target} 423 */ 424 target: function() 425 { 426 if (!this._script) 427 return null; 428 return this._script.target(); 429 }, 430 431 dispose: function() 432 { 433 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 434 }, 435 436 /** 437 * @param {string} sourceMapURL 438 */ 439 addSourceMapURL: function(sourceMapURL) 440 { 441 if (!this._script) 442 return; 443 this._script.addSourceMapURL(sourceMapURL); 444 }, 445 446 __proto__: WebInspector.Object.prototype 447 } 448