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.NetworkWorkspaceBinding} networkWorkspaceBinding 37 * @param {!WebInspector.DebuggerWorkspaceBinding} debuggerWorkspaceBinding 38 */ 39 WebInspector.CompilerScriptMapping = function(debuggerModel, workspace, networkWorkspaceBinding, debuggerWorkspaceBinding) 40 { 41 this._target = debuggerModel.target(); 42 this._debuggerModel = debuggerModel; 43 this._workspace = workspace; 44 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this); 45 this._networkWorkspaceBinding = networkWorkspaceBinding; 46 this._debuggerWorkspaceBinding = debuggerWorkspaceBinding; 47 48 /** @type {!Object.<string, !WebInspector.SourceMap>} */ 49 this._sourceMapForSourceMapURL = {}; 50 /** @type {!Object.<string, !Array.<function(?WebInspector.SourceMap)>>} */ 51 this._pendingSourceMapLoadingCallbacks = {}; 52 /** @type {!Object.<string, !WebInspector.SourceMap>} */ 53 this._sourceMapForScriptId = {}; 54 /** @type {!Map.<!WebInspector.SourceMap, !WebInspector.Script>} */ 55 this._scriptForSourceMap = new Map(); 56 /** @type {!StringMap.<!WebInspector.SourceMap>} */ 57 this._sourceMapForURL = new StringMap(); 58 debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this); 59 } 60 61 WebInspector.CompilerScriptMapping.prototype = { 62 /** 63 * @param {!WebInspector.DebuggerModel.Location} rawLocation 64 * @return {?WebInspector.UILocation} 65 */ 66 rawLocationToUILocation: function(rawLocation) 67 { 68 var debuggerModelLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (rawLocation); 69 var sourceMap = this._sourceMapForScriptId[debuggerModelLocation.scriptId]; 70 if (!sourceMap) 71 return null; 72 var lineNumber = debuggerModelLocation.lineNumber; 73 var columnNumber = debuggerModelLocation.columnNumber || 0; 74 var entry = sourceMap.findEntry(lineNumber, columnNumber); 75 if (!entry || entry.length === 2) 76 return null; 77 var url = /** @type {string} */ (entry[2]); 78 var uiSourceCode = this._workspace.uiSourceCodeForURL(url); 79 if (!uiSourceCode) 80 return null; 81 return uiSourceCode.uiLocation(/** @type {number} */ (entry[3]), /** @type {number} */ (entry[4])); 82 }, 83 84 /** 85 * @param {!WebInspector.UISourceCode} uiSourceCode 86 * @param {number} lineNumber 87 * @param {number} columnNumber 88 * @return {?WebInspector.DebuggerModel.Location} 89 */ 90 uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) 91 { 92 if (!uiSourceCode.url) 93 return null; 94 var sourceMap = this._sourceMapForURL.get(uiSourceCode.url); 95 if (!sourceMap) 96 return null; 97 var script = /** @type {!WebInspector.Script} */ (this._scriptForSourceMap.get(sourceMap)); 98 console.assert(script); 99 var mappingSearchLinesCount = 5; 100 // We do not require precise (breakpoint) location but limit the number of lines to search or mapping. 101 var entry = sourceMap.findEntryReversed(uiSourceCode.url, lineNumber, mappingSearchLinesCount); 102 if (!entry) 103 return null; 104 return this._debuggerModel.createRawLocation(script, /** @type {number} */ (entry[0]), /** @type {number} */ (entry[1])); 105 }, 106 107 /** 108 * @param {!WebInspector.Script} script 109 */ 110 addScript: function(script) 111 { 112 this._debuggerWorkspaceBinding.pushSourceMapping(script, this); 113 script.addEventListener(WebInspector.Script.Events.SourceMapURLAdded, this._sourceMapURLAdded.bind(this)); 114 this._processScript(script); 115 }, 116 117 /** 118 * @param {!WebInspector.Event} event 119 */ 120 _sourceMapURLAdded: function(event) 121 { 122 var script = /** @type {!WebInspector.Script} */ (event.target); 123 this._processScript(script); 124 }, 125 126 /** 127 * @param {!WebInspector.Script} script 128 */ 129 _processScript: function(script) 130 { 131 this.loadSourceMapForScript(script, sourceMapLoaded.bind(this)); 132 133 /** 134 * @param {?WebInspector.SourceMap} sourceMap 135 * @this {WebInspector.CompilerScriptMapping} 136 */ 137 function sourceMapLoaded(sourceMap) 138 { 139 if (!sourceMap) 140 return; 141 142 if (this._scriptForSourceMap.get(sourceMap)) { 143 this._sourceMapForScriptId[script.scriptId] = sourceMap; 144 this._debuggerWorkspaceBinding.updateLocations(script); 145 return; 146 } 147 148 this._sourceMapForScriptId[script.scriptId] = sourceMap; 149 this._scriptForSourceMap.set(sourceMap, script); 150 151 var sourceURLs = sourceMap.sources(); 152 var missingSources = []; 153 for (var i = 0; i < sourceURLs.length; ++i) { 154 var sourceURL = sourceURLs[i]; 155 if (this._sourceMapForURL.get(sourceURL)) 156 continue; 157 this._sourceMapForURL.set(sourceURL, sourceMap); 158 if (!this._workspace.hasMappingForURL(sourceURL) && !this._workspace.uiSourceCodeForURL(sourceURL)) { 159 var contentProvider = sourceMap.sourceContentProvider(sourceURL, WebInspector.resourceTypes.Script); 160 this._networkWorkspaceBinding.addFileForURL(sourceURL, contentProvider, script.isContentScript()); 161 } 162 var uiSourceCode = this._workspace.uiSourceCodeForURL(sourceURL); 163 if (uiSourceCode) { 164 this._bindUISourceCode(uiSourceCode); 165 } else { 166 if (missingSources.length < 3) 167 missingSources.push(sourceURL); 168 else if (missingSources.peekLast() !== "\u2026") 169 missingSources.push("\u2026"); 170 } 171 } 172 if (missingSources.length) { 173 WebInspector.console.warn( 174 WebInspector.UIString("Source map %s points to the files missing from the workspace: [%s]", 175 sourceMap.url(), missingSources.join(", "))); 176 } 177 178 this._debuggerWorkspaceBinding.updateLocations(script); 179 } 180 }, 181 182 /** 183 * @return {boolean} 184 */ 185 isIdentity: function() 186 { 187 return false; 188 }, 189 190 /** 191 * @param {!WebInspector.UISourceCode} uiSourceCode 192 * @param {number} lineNumber 193 * @return {boolean} 194 */ 195 uiLineHasMapping: function(uiSourceCode, lineNumber) 196 { 197 if (!uiSourceCode.url) 198 return true; 199 var sourceMap = this._sourceMapForURL.get(uiSourceCode.url); 200 if (!sourceMap) 201 return true; 202 return !!sourceMap.findEntryReversed(uiSourceCode.url, lineNumber, 0); 203 }, 204 205 /** 206 * @param {!WebInspector.UISourceCode} uiSourceCode 207 */ 208 _bindUISourceCode: function(uiSourceCode) 209 { 210 this._debuggerWorkspaceBinding.setSourceMapping(this._target, uiSourceCode, this); 211 }, 212 213 /** 214 * @param {!WebInspector.UISourceCode} uiSourceCode 215 */ 216 _unbindUISourceCode: function(uiSourceCode) 217 { 218 this._debuggerWorkspaceBinding.setSourceMapping(this._target, uiSourceCode, null); 219 }, 220 221 /** 222 * @param {!WebInspector.Event} event 223 */ 224 _uiSourceCodeAddedToWorkspace: function(event) 225 { 226 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); 227 if (!uiSourceCode.url || !this._sourceMapForURL.get(uiSourceCode.url)) 228 return; 229 this._bindUISourceCode(uiSourceCode); 230 }, 231 232 /** 233 * @param {!WebInspector.Script} script 234 * @param {function(?WebInspector.SourceMap)} callback 235 */ 236 loadSourceMapForScript: function(script, callback) 237 { 238 // script.sourceURL can be a random string, but is generally an absolute path -> complete it to inspected page url for 239 // relative links. 240 if (!script.sourceMapURL) { 241 callback(null); 242 return; 243 } 244 var scriptURL = WebInspector.ParsedURL.completeURL(script.target().resourceTreeModel.inspectedPageURL(), script.sourceURL); 245 if (!scriptURL) { 246 callback(null); 247 return; 248 } 249 var sourceMapURL = WebInspector.ParsedURL.completeURL(scriptURL, script.sourceMapURL); 250 if (!sourceMapURL) { 251 callback(null); 252 return; 253 } 254 255 var sourceMap = this._sourceMapForSourceMapURL[sourceMapURL]; 256 if (sourceMap) { 257 callback(sourceMap); 258 return; 259 } 260 261 var pendingCallbacks = this._pendingSourceMapLoadingCallbacks[sourceMapURL]; 262 if (pendingCallbacks) { 263 pendingCallbacks.push(callback); 264 return; 265 } 266 267 pendingCallbacks = [callback]; 268 this._pendingSourceMapLoadingCallbacks[sourceMapURL] = pendingCallbacks; 269 270 WebInspector.SourceMap.load(sourceMapURL, scriptURL, sourceMapLoaded.bind(this)); 271 272 /** 273 * @param {?WebInspector.SourceMap} sourceMap 274 * @this {WebInspector.CompilerScriptMapping} 275 */ 276 function sourceMapLoaded(sourceMap) 277 { 278 var url = /** @type {string} */ (sourceMapURL); 279 var callbacks = this._pendingSourceMapLoadingCallbacks[url]; 280 delete this._pendingSourceMapLoadingCallbacks[url]; 281 if (!callbacks) 282 return; 283 if (sourceMap) 284 this._sourceMapForSourceMapURL[url] = sourceMap; 285 for (var i = 0; i < callbacks.length; ++i) 286 callbacks[i](sourceMap); 287 } 288 }, 289 290 _debuggerReset: function() 291 { 292 /** 293 * @param {string} sourceURL 294 * @this {WebInspector.CompilerScriptMapping} 295 */ 296 function unbindUISourceCodeForURL(sourceURL) 297 { 298 var uiSourceCode = this._workspace.uiSourceCodeForURL(sourceURL); 299 if (!uiSourceCode) 300 return; 301 this._unbindUISourceCode(uiSourceCode); 302 } 303 304 this._sourceMapForURL.keys().forEach(unbindUISourceCodeForURL.bind(this)); 305 306 this._sourceMapForSourceMapURL = {}; 307 this._pendingSourceMapLoadingCallbacks = {}; 308 this._sourceMapForScriptId = {}; 309 this._scriptForSourceMap.clear(); 310 this._sourceMapForURL.clear(); 311 }, 312 313 dispose: function() 314 { 315 this._workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this); 316 } 317 } 318