blob: 6259e9a3f421e3718d86d75d2017f242ba7f219d [file] [log] [blame]
/*
* Copyright (C) 2012 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.
*/
/**
* @constructor
* @implements {WebInspector.SourceMapping}
* @param {WebInspector.Workspace} workspace
* @param {WebInspector.NetworkWorkspaceProvider} networkWorkspaceProvider
*/
WebInspector.CompilerScriptMapping = function(workspace, networkWorkspaceProvider)
{
this._workspace = workspace;
this._networkWorkspaceProvider = networkWorkspaceProvider;
/** @type {Object.<string, WebInspector.SourceMapParser>} */
this._sourceMapForSourceMapURL = {};
/** @type {Object.<string, WebInspector.SourceMapParser>} */
this._sourceMapForScriptId = {};
this._scriptForSourceMap = new Map();
/** @type {Object.<string, WebInspector.SourceMapParser>} */
this._sourceMapForURL = {};
/** @type {Object.<string, WebInspector.UISourceCode>} */
this._originalUISourceCodeForScriptId = {};
this._scriptForOriginalUISourceCode = new Map();
this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectWillReset, this._reset, this);
}
WebInspector.CompilerScriptMapping.prototype = {
/**
* @param {WebInspector.RawLocation} rawLocation
* @return {WebInspector.UILocation}
*/
rawLocationToUILocation: function(rawLocation)
{
var debuggerModelLocation = /** @type {WebInspector.DebuggerModel.Location} */ (rawLocation);
var sourceMap = this._sourceMapForScriptId[debuggerModelLocation.scriptId];
var lineNumber = debuggerModelLocation.lineNumber;
var columnNumber = debuggerModelLocation.columnNumber || 0;
var entry = sourceMap.findEntry(lineNumber, columnNumber);
if (entry.length === 2) {
var temporaryUISourceCode = this._originalUISourceCodeForScriptId[debuggerModelLocation.scriptId];
return new WebInspector.UILocation(temporaryUISourceCode, lineNumber, columnNumber);
}
var uiSourceCode = this._workspace.uiSourceCodeForURL(entry[2]);
return new WebInspector.UILocation(uiSourceCode, entry[3], entry[4]);
},
/**
* @param {WebInspector.UISourceCode} uiSourceCode
* @param {number} lineNumber
* @param {number} columnNumber
* @return {WebInspector.DebuggerModel.Location}
*/
uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
{
var script = this._scriptForOriginalUISourceCode.get(uiSourceCode);
if (script)
return WebInspector.debuggerModel.createRawLocation(script, lineNumber, columnNumber);
var sourceMap = this._sourceMapForURL[uiSourceCode.url];
var entry = sourceMap.findEntryReversed(uiSourceCode.url, lineNumber);
return WebInspector.debuggerModel.createRawLocation(this._scriptForSourceMap.get(sourceMap), entry[0], entry[1]);
},
/**
* @param {WebInspector.Script} script
*/
addScript: function(script)
{
// FIXME: We should only create temporary uiSourceCodes on demand and should set this as a mapping to
// relevant uiSourceCodes added by NetworkUISourceCodeProvider.
var originalUISourceCode = this._workspace.addTemporaryUISourceCode(script.sourceURL, script, false);
originalUISourceCode.setSourceMapping(this);
this._originalUISourceCodeForScriptId[script.scriptId] = originalUISourceCode;
this._scriptForOriginalUISourceCode.put(originalUISourceCode, script);
var sourceMap = this.loadSourceMapForScript(script);
if (this._scriptForSourceMap.get(sourceMap)) {
this._sourceMapForScriptId[script.scriptId] = sourceMap;
script.setSourceMapping(this);
return;
}
var sourceURLs = sourceMap.sources();
for (var i = 0; i < sourceURLs.length; ++i) {
var sourceURL = sourceURLs[i];
if (this._workspace.uiSourceCodeForURL(sourceURL))
continue;
this._sourceMapForURL[sourceURL] = sourceMap;
var sourceContent = sourceMap.sourceContent(sourceURL);
var contentProvider;
if (sourceContent)
contentProvider = new WebInspector.StaticContentProvider(WebInspector.resourceTypes.Script, sourceContent);
else
contentProvider = new WebInspector.CompilerSourceMappingContentProvider(sourceURL);
this._networkWorkspaceProvider.addFile(sourceURL, contentProvider, true);
var uiSourceCode = this._workspace.uiSourceCodeForURL(sourceURL);
uiSourceCode.setSourceMapping(this);
uiSourceCode.isContentScript = script.isContentScript;
}
this._sourceMapForScriptId[script.scriptId] = sourceMap;
this._scriptForSourceMap.put(sourceMap, script);
script.setSourceMapping(this);
},
/**
* @param {WebInspector.Script} script
* @return {WebInspector.SourceMapParser}
*/
loadSourceMapForScript: function(script)
{
var sourceMapURL = WebInspector.SourceMapParser.prototype._canonicalizeURL(script.sourceMapURL, script.sourceURL);
var sourceMap = this._sourceMapForSourceMapURL[sourceMapURL];
if (sourceMap)
return sourceMap;
try {
// FIXME: make sendRequest async.
var response = InspectorFrontendHost.loadResourceSynchronously(sourceMapURL);
if (response.slice(0, 3) === ")]}")
response = response.substring(response.indexOf('\n'));
var payload = /** @type {WebInspector.SourceMapPayload} */ (JSON.parse(response));
sourceMap = new WebInspector.SourceMapParser(sourceMapURL, payload);
} catch(e) {
console.error(e.message);
return null;
}
this._sourceMapForSourceMapURL[sourceMapURL] = sourceMap;
return sourceMap;
},
_reset: function()
{
this._sourceMapForSourceMapURL = {};
this._sourceMapForScriptId = {};
this._scriptForSourceMap = new Map();
this._sourceMapForURL = {};
this._originalUISourceCodeForScriptId = {};
this._scriptForOriginalUISourceCode = new Map();
}
}
/**
* @constructor
*/
WebInspector.SourceMapPayload = function()
{
this.sections = [];
this.mappings = "";
this.sourceRoot = "";
this.sources = [];
}
/**
* Implements Source Map V3 consumer. See http://code.google.com/p/closure-compiler/wiki/SourceMaps
* for format description.
* @constructor
* @param {string} sourceMappingURL
* @param {WebInspector.SourceMapPayload} payload
*/
WebInspector.SourceMapParser = function(sourceMappingURL, payload)
{
if (!WebInspector.SourceMapParser.prototype._base64Map) {
const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
WebInspector.SourceMapParser.prototype._base64Map = {};
for (var i = 0; i < base64Digits.length; ++i)
WebInspector.SourceMapParser.prototype._base64Map[base64Digits.charAt(i)] = i;
}
this._sourceMappingURL = sourceMappingURL;
this._mappings = [];
this._reverseMappingsBySourceURL = {};
this._sourceContentByURL = {};
this._parseMappingPayload(payload);
}
WebInspector.SourceMapParser.prototype = {
/**
* @return {Array.<string>}
*/
sources: function()
{
var sources = [];
for (var sourceURL in this._reverseMappingsBySourceURL)
sources.push(sourceURL);
return sources;
},
sourceContent: function(sourceURL)
{
return this._sourceContentByURL[sourceURL];
},
findEntry: function(lineNumber, columnNumber)
{
var first = 0;
var count = this._mappings.length;
while (count > 1) {
var step = count >> 1;
var middle = first + step;
var mapping = this._mappings[middle];
if (lineNumber < mapping[0] || (lineNumber == mapping[0] && columnNumber < mapping[1]))
count = step;
else {
first = middle;
count -= step;
}
}
return this._mappings[first];
},
findEntryReversed: function(sourceURL, lineNumber)
{
var mappings = this._reverseMappingsBySourceURL[sourceURL];
for ( ; lineNumber < mappings.length; ++lineNumber) {
var mapping = mappings[lineNumber];
if (mapping)
return mapping;
}
return this._mappings[0];
},
_parseMappingPayload: function(mappingPayload)
{
if (mappingPayload.sections)
this._parseSections(mappingPayload.sections);
else
this._parseMap(mappingPayload, 0, 0);
},
/**
* @param {Array.<SourceMapV3.Section>} sections
*/
_parseSections: function(sections)
{
for (var i = 0; i < sections.length; ++i) {
var section = sections[i];
this._parseMap(section.map, section.offset.line, section.offset.column)
}
},
/**
* @param {SourceMapV3} map
* @param {number} lineNumber
* @param {number} columnNumber
*/
_parseMap: function(map, lineNumber, columnNumber)
{
var sourceIndex = 0;
var sourceLineNumber = 0;
var sourceColumnNumber = 0;
var nameIndex = 0;
var sources = [];
for (var i = 0; i < map.sources.length; ++i) {
var sourceURL = map.sources[i];
if (map.sourceRoot)
sourceURL = map.sourceRoot + "/" + sourceURL;
var url = this._canonicalizeURL(sourceURL, this._sourceMappingURL);
sources.push(url);
if (!this._reverseMappingsBySourceURL[url])
this._reverseMappingsBySourceURL[url] = [];
if (map.sourcesContent && map.sourcesContent[i])
this._sourceContentByURL[url] = map.sourcesContent[i];
}
var stringCharIterator = new WebInspector.SourceMapParser.StringCharIterator(map.mappings);
var sourceURL = sources[sourceIndex];
var reverseMappings = this._reverseMappingsBySourceURL[sourceURL];
while (true) {
if (stringCharIterator.peek() === ",")
stringCharIterator.next();
else {
while (stringCharIterator.peek() === ";") {
lineNumber += 1;
columnNumber = 0;
stringCharIterator.next();
}
if (!stringCharIterator.hasNext())
break;
}
columnNumber += this._decodeVLQ(stringCharIterator);
if (this._isSeparator(stringCharIterator.peek())) {
this._mappings.push([lineNumber, columnNumber]);
continue;
}
var sourceIndexDelta = this._decodeVLQ(stringCharIterator);
if (sourceIndexDelta) {
sourceIndex += sourceIndexDelta;
sourceURL = sources[sourceIndex];
reverseMappings = this._reverseMappingsBySourceURL[sourceURL];
}
sourceLineNumber += this._decodeVLQ(stringCharIterator);
sourceColumnNumber += this._decodeVLQ(stringCharIterator);
if (!this._isSeparator(stringCharIterator.peek()))
nameIndex += this._decodeVLQ(stringCharIterator);
this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]);
if (!reverseMappings[sourceLineNumber])
reverseMappings[sourceLineNumber] = [lineNumber, columnNumber];
}
},
_isSeparator: function(char)
{
return char === "," || char === ";";
},
_decodeVLQ: function(stringCharIterator)
{
// Read unsigned value.
var result = 0;
var shift = 0;
do {
var digit = this._base64Map[stringCharIterator.next()];
result += (digit & this._VLQ_BASE_MASK) << shift;
shift += this._VLQ_BASE_SHIFT;
} while (digit & this._VLQ_CONTINUATION_MASK);
// Fix the sign.
var negative = result & 1;
result >>= 1;
return negative ? -result : result;
},
_canonicalizeURL: function(url, baseURL)
{
if (!url || !baseURL || url.asParsedURL() || url.substring(0, 5) === "data:")
return url;
var base = baseURL.asParsedURL();
if (!base)
return url;
var baseHost = base.scheme + "://" + base.host + (base.port ? ":" + base.port : "");
if (url[0] === "/")
return baseHost + url;
return baseHost + base.folderPathComponents + "/" + url;
},
_VLQ_BASE_SHIFT: 5,
_VLQ_BASE_MASK: (1 << 5) - 1,
_VLQ_CONTINUATION_MASK: 1 << 5
}
/**
* @constructor
*/
WebInspector.SourceMapParser.StringCharIterator = function(string)
{
this._string = string;
this._position = 0;
}
WebInspector.SourceMapParser.StringCharIterator.prototype = {
next: function()
{
return this._string.charAt(this._position++);
},
peek: function()
{
return this._string.charAt(this._position);
},
hasNext: function()
{
return this._position < this._string.length;
}
}