blob: 44c6d3941241c5a70764ff7101c7e74ba4701523 [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.ScriptSourceMapping}
* @param {WebInspector.Workspace} workspace
*/
WebInspector.ResourceScriptMapping = function(workspace)
{
this._workspace = workspace;
this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this);
WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this);
this._initialize();
}
WebInspector.ResourceScriptMapping.prototype = {
/**
* @param {WebInspector.RawLocation} rawLocation
* @return {WebInspector.UILocation}
*/
rawLocationToUILocation: function(rawLocation)
{
var debuggerModelLocation = /** @type {WebInspector.DebuggerModel.Location} */ (rawLocation);
var script = WebInspector.debuggerModel.scriptForId(debuggerModelLocation.scriptId);
var uiSourceCode = this._workspaceUISourceCodeForScript(script);
if (!uiSourceCode)
return null;
var scriptFile = uiSourceCode.scriptFile();
if (scriptFile && ((scriptFile.hasDivergedFromVM() && !scriptFile.isMergingToVM()) || scriptFile.isDivergingFromVM()))
return null;
return new WebInspector.UILocation(uiSourceCode, debuggerModelLocation.lineNumber, debuggerModelLocation.columnNumber || 0);
},
/**
* @param {WebInspector.UISourceCode} uiSourceCode
* @param {number} lineNumber
* @param {number} columnNumber
* @return {WebInspector.DebuggerModel.Location}
*/
uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
{
var scripts = this._scriptsForUISourceCode(uiSourceCode);
console.assert(scripts.length);
return WebInspector.debuggerModel.createRawLocation(scripts[0], lineNumber, columnNumber);
},
/**
* @param {WebInspector.Script} script
*/
addScript: function(script)
{
if (script.isAnonymousScript())
return;
script.pushSourceMapping(this);
var scriptsForSourceURL = script.isInlineScript() ? this._inlineScriptsForSourceURL : this._nonInlineScriptsForSourceURL;
scriptsForSourceURL.put(script.sourceURL, scriptsForSourceURL.get(script.sourceURL) || []);
scriptsForSourceURL.get(script.sourceURL).push(script);
var uiSourceCode = this._workspaceUISourceCodeForScript(script);
if (!uiSourceCode)
return;
this._bindUISourceCodeToScripts(uiSourceCode, [script]);
},
_uiSourceCodeAddedToWorkspace: function(event)
{
var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.data);
if (!uiSourceCode.url)
return;
var scripts = this._scriptsForUISourceCode(uiSourceCode);
if (!scripts.length)
return;
this._bindUISourceCodeToScripts(uiSourceCode, scripts);
},
/**
* @param {WebInspector.UISourceCode} uiSourceCode
*/
_hasMergedToVM: function(uiSourceCode)
{
var scripts = this._scriptsForUISourceCode(uiSourceCode);
if (!scripts.length)
return;
for (var i = 0; i < scripts.length; ++i)
scripts[i].updateLocations();
},
/**
* @param {WebInspector.UISourceCode} uiSourceCode
*/
_hasDivergedFromVM: function(uiSourceCode)
{
var scripts = this._scriptsForUISourceCode(uiSourceCode);
if (!scripts.length)
return;
for (var i = 0; i < scripts.length; ++i)
scripts[i].updateLocations();
},
/**
* @param {WebInspector.Script} script
* @return {WebInspector.UISourceCode}
*/
_workspaceUISourceCodeForScript: function(script)
{
if (script.isAnonymousScript())
return null;
return this._workspace.uiSourceCodeForURL(script.sourceURL);
},
/**
* @param {WebInspector.UISourceCode} uiSourceCode
* @return {Array.<WebInspector.Script>}
*/
_scriptsForUISourceCode: function(uiSourceCode)
{
var isInlineScript;
switch (uiSourceCode.contentType()) {
case WebInspector.resourceTypes.Document:
isInlineScript = true;
break;
case WebInspector.resourceTypes.Script:
isInlineScript = false;
break;
default:
return [];
}
if (!uiSourceCode.url)
return [];
var scriptsForSourceURL = isInlineScript ? this._inlineScriptsForSourceURL : this._nonInlineScriptsForSourceURL;
return scriptsForSourceURL.get(uiSourceCode.url) || [];
},
/**
* @param {WebInspector.UISourceCode} uiSourceCode
* @param {Array.<WebInspector.Script>} scripts
*/
_bindUISourceCodeToScripts: function(uiSourceCode, scripts)
{
console.assert(scripts.length);
var scriptFile = new WebInspector.ResourceScriptFile(this, uiSourceCode, scripts);
uiSourceCode.setScriptFile(scriptFile);
for (var i = 0; i < scripts.length; ++i)
scripts[i].updateLocations();
uiSourceCode.setSourceMapping(this);
},
/**
* @param {WebInspector.UISourceCode} uiSourceCode
* @param {Array.<WebInspector.Script>} scripts
*/
_unbindUISourceCodeFromScripts: function(uiSourceCode, scripts)
{
console.assert(scripts.length);
var scriptFile = /** @type {WebInspector.ResourceScriptFile} */ (uiSourceCode.scriptFile());
if (scriptFile) {
scriptFile.dispose();
uiSourceCode.setScriptFile(null);
}
uiSourceCode.setSourceMapping(null);
},
_initialize: function()
{
/** @type {StringMap.<!Array.<!WebInspector.Script>>} */
this._inlineScriptsForSourceURL = new StringMap();
/** @type {StringMap.<!Array.<!WebInspector.Script>>} */
this._nonInlineScriptsForSourceURL = new StringMap();
},
_debuggerReset: function()
{
/**
* @param {!Array.<!WebInspector.Script>} scripts
*/
function unbindUISourceCodesForScripts(scripts)
{
if (!scripts.length)
return;
var uiSourceCode = this._workspaceUISourceCodeForScript(scripts[0]);
if (!uiSourceCode)
return;
this._unbindUISourceCodeFromScripts(uiSourceCode, scripts);
}
this._inlineScriptsForSourceURL.values().forEach(unbindUISourceCodesForScripts.bind(this));
this._nonInlineScriptsForSourceURL.values().forEach(unbindUISourceCodesForScripts.bind(this));
this._initialize();
},
}
/**
* @interface
* @extends {WebInspector.EventTarget}
*/
WebInspector.ScriptFile = function()
{
}
WebInspector.ScriptFile.Events = {
DidMergeToVM: "DidMergeToVM",
DidDivergeFromVM: "DidDivergeFromVM",
}
WebInspector.ScriptFile.prototype = {
/**
* @return {boolean}
*/
hasDivergedFromVM: function() { return false; },
/**
* @return {boolean}
*/
isDivergingFromVM: function() { return false; },
/**
* @return {boolean}
*/
isMergingToVM: function() { return false; },
checkMapping: function() { },
}
/**
* @constructor
* @implements {WebInspector.ScriptFile}
* @extends {WebInspector.Object}
* @param {WebInspector.ResourceScriptMapping} resourceScriptMapping
* @param {WebInspector.UISourceCode} uiSourceCode
*/
WebInspector.ResourceScriptFile = function(resourceScriptMapping, uiSourceCode, scripts)
{
console.assert(scripts.length);
WebInspector.ScriptFile.call(this);
this._resourceScriptMapping = resourceScriptMapping;
this._uiSourceCode = uiSourceCode;
if (this._uiSourceCode.contentType() === WebInspector.resourceTypes.Script)
this._script = scripts[0];
this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
this._update();
}
WebInspector.ResourceScriptFile.prototype = {
_workingCopyCommitted: function(event)
{
/**
* @param {?string} error
* @param {DebuggerAgent.SetScriptSourceError=} errorData
*/
function innerCallback(error, errorData)
{
if (error) {
this._update();
WebInspector.LiveEditSupport.logDetailedError(error, errorData, this._script);
return;
}
this._scriptSource = source;
this._update();
WebInspector.LiveEditSupport.logSuccess();
}
if (!this._script)
return;
var source = this._uiSourceCode.workingCopy();
if (this._script.hasSourceURL && !this._sourceEndsWithSourceURL(source))
source += "\n //# sourceURL=" + this._script.sourceURL;
WebInspector.debuggerModel.setScriptSource(this._script.scriptId, source, innerCallback.bind(this));
},
/**
* @return {boolean}
*/
_isDiverged: function()
{
if (this._uiSourceCode.formatted())
return false;
if (this._uiSourceCode.isDirty())
return true;
if (!this._script)
return false;
if (typeof this._scriptSource === "undefined")
return false;
return !this._sourceMatchesScriptSource(this._uiSourceCode.workingCopy(), this._scriptSource);
},
/**
* @param {string} source
* @param {string} scriptSource
* @return {boolean}
*/
_sourceMatchesScriptSource: function(source, scriptSource)
{
if (!scriptSource.startsWith(source))
return false;
var scriptSourceTail = scriptSource.substr(source.length).trim();
return !scriptSourceTail || !!scriptSourceTail.match(/^\/\/[@#]\ssourceURL=\s*(\S*?)\s*$/m);
},
/**
* @param {string} source
* @return {boolean}
*/
_sourceEndsWithSourceURL: function(source)
{
return !!source.match(/\/\/[@#]\ssourceURL=\s*(\S*?)\s*$/m);
},
/**
* @param {WebInspector.Event} event
*/
_workingCopyChanged: function(event)
{
this._update();
},
_update: function()
{
if (this._isDiverged() && !this._hasDivergedFromVM)
this._divergeFromVM();
else if (!this._isDiverged() && this._hasDivergedFromVM)
this._mergeToVM();
},
_divergeFromVM: function()
{
this._isDivergingFromVM = true;
this._resourceScriptMapping._hasDivergedFromVM(this._uiSourceCode);
delete this._isDivergingFromVM;
this._hasDivergedFromVM = true;
this.dispatchEventToListeners(WebInspector.ScriptFile.Events.DidDivergeFromVM, this._uiSourceCode);
},
_mergeToVM: function()
{
delete this._hasDivergedFromVM;
this._isMergingToVM = true;
this._resourceScriptMapping._hasMergedToVM(this._uiSourceCode);
delete this._isMergingToVM;
this.dispatchEventToListeners(WebInspector.ScriptFile.Events.DidMergeToVM, this._uiSourceCode);
},
/**
* @return {boolean}
*/
hasDivergedFromVM: function()
{
return this._hasDivergedFromVM;
},
/**
* @return {boolean}
*/
isDivergingFromVM: function()
{
return this._isDivergingFromVM;
},
/**
* @return {boolean}
*/
isMergingToVM: function()
{
return this._isMergingToVM;
},
checkMapping: function()
{
if (!this._script)
return;
if (typeof this._scriptSource !== "undefined")
return;
this._script.requestContent(callback.bind(this));
/**
* @param {?string} source
*/
function callback(source)
{
this._scriptSource = source;
this._update();
}
},
dispose: function()
{
this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
},
__proto__: WebInspector.Object.prototype
}