1 /* 2 * Copyright (C) 2008 Apple 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 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 /** 27 * @constructor 28 * @extends {WebInspector.Object} 29 * @implements {WebInspector.ContentProvider} 30 * @param {string} scriptId 31 * @param {string} sourceURL 32 * @param {number} startLine 33 * @param {number} startColumn 34 * @param {number} endLine 35 * @param {number} endColumn 36 * @param {boolean} isContentScript 37 * @param {string=} sourceMapURL 38 * @param {boolean=} hasSourceURL 39 */ 40 WebInspector.Script = function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL) 41 { 42 this.scriptId = scriptId; 43 this.sourceURL = sourceURL; 44 this.lineOffset = startLine; 45 this.columnOffset = startColumn; 46 this.endLine = endLine; 47 this.endColumn = endColumn; 48 this.isContentScript = isContentScript; 49 this.sourceMapURL = sourceMapURL; 50 this.hasSourceURL = hasSourceURL; 51 /** @type {!Set.<!WebInspector.Script.Location>} */ 52 this._locations = new Set(); 53 /** @type {!Array.<!WebInspector.SourceMapping>} */ 54 this._sourceMappings = []; 55 } 56 57 WebInspector.Script.Events = { 58 ScriptEdited: "ScriptEdited", 59 } 60 61 WebInspector.Script.snippetSourceURLPrefix = "snippets:///"; 62 63 WebInspector.Script.prototype = { 64 /** 65 * @return {string} 66 */ 67 contentURL: function() 68 { 69 return this.sourceURL; 70 }, 71 72 /** 73 * @return {WebInspector.ResourceType} 74 */ 75 contentType: function() 76 { 77 return WebInspector.resourceTypes.Script; 78 }, 79 80 /** 81 * @param {function(?string,boolean,string)} callback 82 */ 83 requestContent: function(callback) 84 { 85 if (this._source) { 86 callback(this._source, false, "text/javascript"); 87 return; 88 } 89 90 /** 91 * @this {WebInspector.Script} 92 * @param {?Protocol.Error} error 93 * @param {string} source 94 */ 95 function didGetScriptSource(error, source) 96 { 97 this._source = error ? "" : source; 98 callback(this._source, false, "text/javascript"); 99 } 100 if (this.scriptId) { 101 // Script failed to parse. 102 DebuggerAgent.getScriptSource(this.scriptId, didGetScriptSource.bind(this)); 103 } else 104 callback("", false, "text/javascript"); 105 }, 106 107 /** 108 * @param {string} query 109 * @param {boolean} caseSensitive 110 * @param {boolean} isRegex 111 * @param {function(Array.<PageAgent.SearchMatch>)} callback 112 */ 113 searchInContent: function(query, caseSensitive, isRegex, callback) 114 { 115 /** 116 * @this {WebInspector.Script} 117 * @param {?Protocol.Error} error 118 * @param {Array.<PageAgent.SearchMatch>} searchMatches 119 */ 120 function innerCallback(error, searchMatches) 121 { 122 if (error) 123 console.error(error); 124 var result = []; 125 for (var i = 0; i < searchMatches.length; ++i) { 126 var searchMatch = new WebInspector.ContentProvider.SearchMatch(searchMatches[i].lineNumber, searchMatches[i].lineContent); 127 result.push(searchMatch); 128 } 129 callback(result || []); 130 } 131 132 if (this.scriptId) { 133 // Script failed to parse. 134 DebuggerAgent.searchInContent(this.scriptId, query, caseSensitive, isRegex, innerCallback.bind(this)); 135 } else 136 callback([]); 137 }, 138 139 /** 140 * @param {string} newSource 141 * @param {function(?Protocol.Error, DebuggerAgent.SetScriptSourceError=, Array.<DebuggerAgent.CallFrame>=)} callback 142 */ 143 editSource: function(newSource, callback) 144 { 145 /** 146 * @this {WebInspector.Script} 147 * @param {?Protocol.Error} error 148 * @param {DebuggerAgent.SetScriptSourceError=} errorData 149 * @param {Array.<DebuggerAgent.CallFrame>=} callFrames 150 * @param {Object=} debugData 151 */ 152 function didEditScriptSource(error, errorData, callFrames, debugData) 153 { 154 // FIXME: support debugData.stack_update_needs_step_in flag by calling WebInspector.debugger_model.callStackModified 155 if (!error) 156 this._source = newSource; 157 callback(error, errorData, callFrames); 158 if (!error) 159 this.dispatchEventToListeners(WebInspector.Script.Events.ScriptEdited, newSource); 160 } 161 if (this.scriptId) { 162 // Script failed to parse. 163 DebuggerAgent.setScriptSource(this.scriptId, newSource, undefined, didEditScriptSource.bind(this)); 164 } else 165 callback("Script failed to parse"); 166 }, 167 168 /** 169 * @return {boolean} 170 */ 171 isInlineScript: function() 172 { 173 var startsAtZero = !this.lineOffset && !this.columnOffset; 174 return !!this.sourceURL && !startsAtZero; 175 }, 176 177 /** 178 * @return {boolean} 179 */ 180 isAnonymousScript: function() 181 { 182 return !this.sourceURL; 183 }, 184 185 /** 186 * @return {boolean} 187 */ 188 isSnippet: function() 189 { 190 return !!this.sourceURL && this.sourceURL.startsWith(WebInspector.Script.snippetSourceURLPrefix); 191 }, 192 193 /** 194 * @param {number} lineNumber 195 * @param {number=} columnNumber 196 * @return {WebInspector.UILocation} 197 */ 198 rawLocationToUILocation: function(lineNumber, columnNumber) 199 { 200 var uiLocation; 201 var rawLocation = new WebInspector.DebuggerModel.Location(this.scriptId, lineNumber, columnNumber || 0); 202 for (var i = this._sourceMappings.length - 1; !uiLocation && i >= 0; --i) 203 uiLocation = this._sourceMappings[i].rawLocationToUILocation(rawLocation); 204 console.assert(uiLocation, "Script raw location can not be mapped to any ui location."); 205 return uiLocation.uiSourceCode.overrideLocation(uiLocation); 206 }, 207 208 /** 209 * @param {!WebInspector.SourceMapping} sourceMapping 210 */ 211 pushSourceMapping: function(sourceMapping) 212 { 213 this._sourceMappings.push(sourceMapping); 214 this.updateLocations(); 215 }, 216 217 updateLocations: function() 218 { 219 var items = this._locations.items(); 220 for (var i = 0; i < items.length; ++i) 221 items[i].update(); 222 }, 223 224 /** 225 * @param {WebInspector.DebuggerModel.Location} rawLocation 226 * @param {function(WebInspector.UILocation):(boolean|undefined)} updateDelegate 227 * @return {WebInspector.Script.Location} 228 */ 229 createLiveLocation: function(rawLocation, updateDelegate) 230 { 231 console.assert(rawLocation.scriptId === this.scriptId); 232 var location = new WebInspector.Script.Location(this, rawLocation, updateDelegate); 233 this._locations.add(location); 234 location.update(); 235 return location; 236 }, 237 238 __proto__: WebInspector.Object.prototype 239 } 240 241 /** 242 * @constructor 243 * @extends {WebInspector.LiveLocation} 244 * @param {WebInspector.Script} script 245 * @param {WebInspector.DebuggerModel.Location} rawLocation 246 * @param {function(WebInspector.UILocation):(boolean|undefined)} updateDelegate 247 */ 248 WebInspector.Script.Location = function(script, rawLocation, updateDelegate) 249 { 250 WebInspector.LiveLocation.call(this, rawLocation, updateDelegate); 251 this._script = script; 252 } 253 254 WebInspector.Script.Location.prototype = { 255 /** 256 * @return {WebInspector.UILocation} 257 */ 258 uiLocation: function() 259 { 260 var debuggerModelLocation = /** @type {WebInspector.DebuggerModel.Location} */ (this.rawLocation()); 261 return this._script.rawLocationToUILocation(debuggerModelLocation.lineNumber, debuggerModelLocation.columnNumber); 262 }, 263 264 dispose: function() 265 { 266 WebInspector.LiveLocation.prototype.dispose.call(this); 267 this._script._locations.remove(this); 268 }, 269 270 __proto__: WebInspector.LiveLocation.prototype 271 } 272