| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| WebInspector.SourceFile = function(id, script, contentChangedDelegate) |
| { |
| this._scripts = [script]; |
| this._contentChangedDelegate = contentChangedDelegate; |
| if (script.sourceURL) |
| this._resource = WebInspector.networkManager.inflightResourceForURL(script.sourceURL) || WebInspector.resourceForURL(script.sourceURL); |
| this._requestContentCallbacks = []; |
| |
| this.id = id; |
| this.url = script.sourceURL; |
| this.isContentScript = script.isContentScript; |
| this.messages = []; |
| this.breakpoints = {}; |
| |
| if (this._hasPendingResource()) |
| this._resource.addEventListener("finished", this.reload.bind(this)); |
| } |
| |
| WebInspector.SourceFile.prototype = { |
| addScript: function(script) |
| { |
| this._scripts.push(script); |
| }, |
| |
| requestContent: function(callback) |
| { |
| if (this._contentLoaded) { |
| callback(this._mimeType, this._content); |
| return; |
| } |
| |
| this._requestContentCallbacks.push(callback); |
| this._requestContent(); |
| }, |
| |
| get content() |
| { |
| return this._content; |
| }, |
| |
| set content(content) |
| { |
| // FIXME: move live edit implementation to SourceFile and remove this setter. |
| this._content = content; |
| }, |
| |
| requestSourceMapping: function(callback) |
| { |
| if (!this._mapping) |
| this._mapping = new WebInspector.SourceMapping(this._scripts); |
| callback(this._mapping); |
| }, |
| |
| forceLoadContent: function(script) |
| { |
| if (!this._hasPendingResource()) |
| return; |
| |
| if (!this._concatenatedScripts) |
| this._concatenatedScripts = {}; |
| if (this._concatenatedScripts[script.sourceID]) |
| return; |
| for (var i = 0; i < this._scripts.length; ++i) |
| this._concatenatedScripts[this._scripts[i].sourceID] = true; |
| |
| this.reload(); |
| |
| if (!this._contentRequested) { |
| this._contentRequested = true; |
| this._loadAndConcatenateScriptsContent(); |
| } |
| }, |
| |
| reload: function() |
| { |
| if (this._contentLoaded) { |
| this._contentLoaded = false; |
| this._contentChangedDelegate(); |
| } else if (this._contentRequested) |
| this._reloadContent = true; |
| else if (this._requestContentCallbacks.length) |
| this._requestContent(); |
| }, |
| |
| _requestContent: function() |
| { |
| if (this._contentRequested) |
| return; |
| |
| this._contentRequested = true; |
| if (this._resource && this._resource.finished) |
| this._loadResourceContent(this._resource); |
| else if (!this._resource) |
| this._loadScriptContent(); |
| else if (this._concatenatedScripts) |
| this._loadAndConcatenateScriptsContent(); |
| else |
| this._contentRequested = false; |
| }, |
| |
| _loadResourceContent: function(resource) |
| { |
| function didRequestContent(text) |
| { |
| if (!text) { |
| this._loadAndConcatenateScriptsContent(); |
| return; |
| } |
| |
| if (resource.type === WebInspector.Resource.Type.Script) |
| this._didRequestContent("text/javascript", text); |
| else { |
| // WebKit html lexer normalizes line endings and scripts are passed to VM with "\n" line endings. |
| // However, resource content has original line endings, so we have to normalize line endings here. |
| this._didRequestContent("text/html", text.replace(/\r\n/g, "\n")); |
| } |
| } |
| resource.requestContent(didRequestContent.bind(this)); |
| }, |
| |
| _loadScriptContent: function() |
| { |
| this._scripts[0].requestSource(this._didRequestContent.bind(this, "text/javascript")); |
| }, |
| |
| _loadAndConcatenateScriptsContent: function() |
| { |
| var scripts = this._scripts.slice(); |
| scripts.sort(function(x, y) { return x.lineOffset - y.lineOffset || x.columnOffset - y.columnOffset; }); |
| var sources = []; |
| function didRequestSource(source) |
| { |
| sources.push(source); |
| if (sources.length < scripts.length) |
| return; |
| if (scripts.length === 1 && !scripts[0].lineOffset && !scripts[0].columnOffset) |
| this._didRequestContent("text/javascript", source); |
| else |
| this._concatenateScriptsContent(scripts, sources); |
| } |
| for (var i = 0; i < scripts.length; ++i) |
| scripts[i].requestSource(didRequestSource.bind(this)); |
| }, |
| |
| _concatenateScriptsContent: function(scripts, sources) |
| { |
| var content = ""; |
| var lineNumber = 0; |
| var columnNumber = 0; |
| var scriptRanges = []; |
| function appendChunk(chunk, script) |
| { |
| var start = { lineNumber: lineNumber, columnNumber: columnNumber }; |
| content += chunk; |
| var lineEndings = chunk.lineEndings(); |
| var lineCount = lineEndings.length; |
| if (lineCount === 1) |
| columnNumber += chunk.length; |
| else { |
| lineNumber += lineCount - 1; |
| columnNumber = lineEndings[lineCount - 1] - lineEndings[lineCount - 2] - 1; |
| } |
| var end = { lineNumber: lineNumber, columnNumber: columnNumber }; |
| if (script) |
| scriptRanges.push({ start: start, end: end, sourceID: script.sourceID }); |
| } |
| |
| var scriptOpenTag = "<script>"; |
| var scriptCloseTag = "</script>"; |
| for (var i = 0; i < scripts.length; ++i) { |
| // Fill the gap with whitespace characters. |
| while (lineNumber < scripts[i].lineOffset) |
| appendChunk("\n"); |
| while (columnNumber < scripts[i].columnOffset - scriptOpenTag.length) |
| appendChunk(" "); |
| |
| // Add script tag. |
| appendChunk(scriptOpenTag); |
| appendChunk(sources[i], scripts[i]); |
| appendChunk(scriptCloseTag); |
| } |
| |
| this._didRequestContent("text/html", content); |
| }, |
| |
| _didRequestContent: function(mimeType, content) |
| { |
| this._contentLoaded = true; |
| this._contentRequested = false; |
| this._mimeType = mimeType; |
| this._content = content; |
| |
| for (var i = 0; i < this._requestContentCallbacks.length; ++i) |
| this._requestContentCallbacks[i](mimeType, content); |
| this._requestContentCallbacks = []; |
| |
| if (this._reloadContent) |
| this.reload(); |
| }, |
| |
| _hasPendingResource: function() |
| { |
| return this._resource && !this._resource.finished; |
| } |
| } |
| |
| WebInspector.FormattedSourceFile = function(sourceFileId, script, contentChangedDelegate, formatter) |
| { |
| WebInspector.SourceFile.call(this, sourceFileId, script, contentChangedDelegate); |
| this._formatter = formatter; |
| } |
| |
| WebInspector.FormattedSourceFile.prototype = { |
| requestSourceMapping: function(callback) |
| { |
| function didRequestContent() |
| { |
| callback(this._mapping); |
| } |
| this.requestContent(didRequestContent.bind(this)); |
| }, |
| |
| _didRequestContent: function(mimeType, text) |
| { |
| function didFormatContent(formattedText, mapping) |
| { |
| this._mapping = new WebInspector.FormattedSourceMapping(this._scripts, text, formattedText, mapping); |
| WebInspector.SourceFile.prototype._didRequestContent.call(this, mimeType, formattedText); |
| } |
| this._formatter.formatContent(text, this._scripts, didFormatContent.bind(this)); |
| } |
| } |
| |
| WebInspector.FormattedSourceFile.prototype.__proto__ = WebInspector.SourceFile.prototype; |
| |
| WebInspector.SourceMapping = function(scripts) |
| { |
| this._sortedScripts = scripts.slice(); |
| this._sortedScripts.sort(function(x, y) { return x.lineOffset - y.lineOffset || x.columnOffset - y.columnOffset; }); |
| } |
| |
| WebInspector.SourceMapping.prototype = { |
| scriptLocationToSourceLine: function(location) |
| { |
| return location.lineNumber; |
| }, |
| |
| sourceLineToScriptLocation: function(lineNumber) |
| { |
| return this._sourceLocationToScriptLocation(lineNumber, 0); |
| }, |
| |
| _sourceLocationToScriptLocation: function(lineNumber, columnNumber) |
| { |
| var closestScript = this._sortedScripts[0]; |
| for (var i = 1; i < this._sortedScripts.length; ++i) { |
| script = this._sortedScripts[i]; |
| if (script.lineOffset > lineNumber || (script.lineOffset === lineNumber && script.columnOffset > columnNumber)) |
| break; |
| closestScript = script; |
| } |
| return { sourceID: closestScript.sourceID, lineNumber: lineNumber, columnNumber: columnNumber }; |
| } |
| } |
| |
| WebInspector.FormattedSourceMapping = function(scripts, originalText, formattedText, mapping) |
| { |
| WebInspector.SourceMapping.call(this, scripts); |
| this._originalLineEndings = originalText.lineEndings(); |
| this._formattedLineEndings = formattedText.lineEndings(); |
| this._mapping = mapping; |
| } |
| |
| WebInspector.FormattedSourceMapping.prototype = { |
| scriptLocationToSourceLine: function(location) |
| { |
| var originalPosition = WebInspector.ScriptFormatter.locationToPosition(this._originalLineEndings, location); |
| var index = this._mapping.original.upperBound(originalPosition - 1); |
| var formattedPosition = this._mapping.formatted[index]; |
| return WebInspector.ScriptFormatter.positionToLocation(this._formattedLineEndings, formattedPosition).lineNumber; |
| }, |
| |
| sourceLineToScriptLocation: function(lineNumber) |
| { |
| var formattedPosition = WebInspector.ScriptFormatter.lineToPosition(this._formattedLineEndings, lineNumber); |
| var index = this._mapping.formatted.upperBound(formattedPosition - 1); |
| var originalPosition = this._mapping.original[index]; |
| var originalLocation = WebInspector.ScriptFormatter.positionToLocation(this._originalLineEndings, originalPosition); |
| return WebInspector.SourceMapping.prototype._sourceLocationToScriptLocation.call(this, originalLocation.lineNumber, originalLocation.columnNumber); |
| } |
| } |
| |
| WebInspector.FormattedSourceMapping.prototype.__proto__ = WebInspector.SourceMapping.prototype; |