1/* 2 * Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.SourceCode = function() 27{ 28 WebInspector.Object.call(this); 29 30 this._pendingContentRequestCallbacks = []; 31 32 this._originalRevision = new WebInspector.SourceCodeRevision(this, null, false); 33 this._currentRevision = this._originalRevision; 34 35 this._sourceMaps = null; 36 this._formatterSourceMap = null; 37}; 38 39WebInspector.SourceCode.Event = { 40 ContentDidChange: "source-code-content-did-change", 41 SourceMapAdded: "source-code-source-map-added", 42 FormatterDidChange: "source-code-formatter-did-change" 43}; 44 45WebInspector.SourceCode.prototype = { 46 constructor: WebInspector.SourceCode, 47 48 // Public 49 50 get displayName() 51 { 52 // Implemented by subclasses. 53 console.error("Needs to be implemented by a subclass."); 54 return ""; 55 }, 56 57 get originalRevision() 58 { 59 return this._originalRevision; 60 }, 61 62 get currentRevision() 63 { 64 return this._currentRevision; 65 }, 66 67 set currentRevision(revision) 68 { 69 console.assert(revision instanceof WebInspector.SourceCodeRevision); 70 if (!(revision instanceof WebInspector.SourceCodeRevision)) 71 return; 72 73 console.assert(revision.sourceCode === this); 74 if (revision.sourceCode !== this) 75 return; 76 77 this._currentRevision = revision; 78 79 this.dispatchEventToListeners(WebInspector.SourceCode.Event.ContentDidChange); 80 }, 81 82 get content() 83 { 84 return this._currentRevision.content; 85 }, 86 87 get contentIsBase64Encoded() 88 { 89 return this._currentRevision.contentIsBase64Encoded; 90 }, 91 92 get sourceMaps() 93 { 94 return this._sourceMaps || []; 95 }, 96 97 addSourceMap: function(sourceMap) 98 { 99 console.assert(sourceMap instanceof WebInspector.SourceMap); 100 101 if (!this._sourceMaps) 102 this._sourceMaps = []; 103 104 this._sourceMaps.push(sourceMap); 105 106 this.dispatchEventToListeners(WebInspector.SourceCode.Event.SourceMapAdded); 107 }, 108 109 get formatterSourceMap() 110 { 111 return this._formatterSourceMap; 112 }, 113 114 set formatterSourceMap(formatterSourceMap) 115 { 116 console.assert(this._formatterSourceMap === null || formatterSourceMap === null); 117 console.assert(formatterSourceMap === null || formatterSourceMap instanceof WebInspector.FormatterSourceMap); 118 119 this._formatterSourceMap = formatterSourceMap; 120 121 this.dispatchEventToListeners(WebInspector.SourceCode.Event.FormatterDidChange); 122 }, 123 124 requestContent: function(callback) 125 { 126 console.assert(typeof callback === "function"); 127 if (typeof callback !== "function") 128 return; 129 130 this._pendingContentRequestCallbacks.push(callback); 131 132 if (this._contentReceived) { 133 // Call _servicePendingContentRequests on a timeout to force callbacks to be asynchronous. 134 if (!this._servicePendingContentRequestsTimeoutIdentifier) 135 this._servicePendingContentRequestsTimeoutIdentifier = setTimeout(this.servicePendingContentRequests.bind(this), 0); 136 } else if (this.canRequestContentFromBackend()) 137 this.requestContentFromBackendIfNeeded(); 138 }, 139 140 createSourceCodeLocation: function(lineNumber, columnNumber) 141 { 142 return new WebInspector.SourceCodeLocation(this, lineNumber, columnNumber); 143 }, 144 145 createLazySourceCodeLocation: function(lineNumber, columnNumber) 146 { 147 return new WebInspector.LazySourceCodeLocation(this, lineNumber, columnNumber); 148 }, 149 150 createSourceCodeTextRange: function(textRange) 151 { 152 return new WebInspector.SourceCodeTextRange(this, textRange); 153 }, 154 155 // Protected 156 157 revisionContentDidChange: function(revision) 158 { 159 if (this._ignoreRevisionContentDidChangeEvent) 160 return; 161 162 if (revision !== this._currentRevision) 163 return; 164 165 this.handleCurrentRevisionContentChange(); 166 167 this.dispatchEventToListeners(WebInspector.SourceCode.Event.ContentDidChange); 168 }, 169 170 handleCurrentRevisionContentChange: function() 171 { 172 // Implemented by subclasses if needed. 173 }, 174 175 get revisionForRequestedContent() 176 { 177 // Implemented by subclasses if needed. 178 return this._originalRevision; 179 }, 180 181 markContentAsStale: function() 182 { 183 this._contentReceived = false; 184 }, 185 186 canRequestContentFromBackend: function() 187 { 188 // Implemented by subclasses. 189 console.error("Needs to be implemented by a subclass."); 190 return false; 191 }, 192 193 requestContentFromBackend: function(callback) 194 { 195 // Implemented by subclasses. 196 console.error("Needs to be implemented by a subclass."); 197 }, 198 199 requestContentFromBackendIfNeeded: function() 200 { 201 console.assert(this.canRequestContentFromBackend()); 202 if (!this.canRequestContentFromBackend()) 203 return; 204 205 if (!this._pendingContentRequestCallbacks.length) 206 return; 207 208 if (this._contentRequestResponsePending) 209 return; 210 211 this._contentRequestResponsePending = true; 212 213 if (this.requestContentFromBackend(this._processContent.bind(this))) 214 return; 215 216 // Since requestContentFromBackend returned false, just call _processContent, 217 // which will cause the pending callbacks to get null content. 218 this._processContent(); 219 }, 220 221 servicePendingContentRequests: function(force) 222 { 223 if (this._servicePendingContentRequestsTimeoutIdentifier) { 224 clearTimeout(this._servicePendingContentRequestsTimeoutIdentifier); 225 delete this._servicePendingContentRequestsTimeoutIdentifier; 226 } 227 228 // Force the content requests to be sent. To do this correctly we also force 229 // _contentReceived to be true so future calls to requestContent go through. 230 if (force) 231 this._contentReceived = true; 232 233 console.assert(this._contentReceived); 234 if (!this._contentReceived) 235 return; 236 237 // Move the callbacks into a local and clear _pendingContentRequestCallbacks so 238 // callbacks that might call requestContent again will not modify the array. 239 var callbacks = this._pendingContentRequestCallbacks; 240 this._pendingContentRequestCallbacks = []; 241 242 for (var i = 0; i < callbacks.length; ++i) 243 callbacks[i](this, this.content, this.contentIsBase64Encoded); 244 }, 245 246 // Private 247 248 _processContent: function(error, content, base64Encoded) 249 { 250 if (error) 251 console.error(error); 252 253 this._contentRequestResponsePending = false; 254 this._contentReceived = true; 255 256 var revision = this.revisionForRequestedContent; 257 258 this._ignoreRevisionContentDidChangeEvent = true; 259 revision.content = content || null; 260 revision.contentIsBase64Encoded = base64Encoded || false; 261 delete this._ignoreRevisionContentDidChangeEvent; 262 263 this.servicePendingContentRequests(); 264 } 265}; 266 267WebInspector.SourceCode.prototype.__proto__ = WebInspector.Object.prototype; 268