blob: c58a0c7a2a2e26720aca11e68cace26b721f7d30 [file] [log] [blame]
/*
* 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;