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