blob: 9d712d0c7fc835da952e0eda184a0b8aea47d65e [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
* @extends {WebInspector.Object}
*/
WebInspector.ScriptSnippetModel = function()
{
this._uiSourceCodeForScriptId = {};
this._scriptForUISourceCode = new Map();
this._snippetJavaScriptSourceForSnippetId = {};
this._snippetStorage = new WebInspector.SnippetStorage("script", "Script snippet #");
this._lastSnippetEvaluationIndexSetting = WebInspector.settings.createSetting("lastSnippetEvaluationIndex", 0);
this._snippetScriptMapping = new WebInspector.SnippetScriptMapping(this);
this._loadSnippets();
}
WebInspector.ScriptSnippetModel.snippetSourceURLPrefix = "snippets:///";
WebInspector.ScriptSnippetModel.prototype = {
/**
* @return {WebInspector.SnippetScriptMapping}
*/
get scriptMapping()
{
return this._snippetScriptMapping;
},
_loadSnippets: function()
{
var snippets = this._snippetStorage.snippets();
for (var i = 0; i < snippets.length; ++i)
this._addScriptSnippet(snippets[i]);
},
/**
* @return {WebInspector.SnippetJavaScriptSource}
*/
createScriptSnippet: function()
{
var snippet = this._snippetStorage.createSnippet();
return this._addScriptSnippet(snippet);
},
/**
* @param {WebInspector.Snippet} snippet
* @return {WebInspector.SnippetJavaScriptSource}
*/
_addScriptSnippet: function(snippet)
{
var snippetJavaScriptSource = new WebInspector.SnippetJavaScriptSource(snippet.id, snippet.name, new WebInspector.SnippetContentProvider(snippet), this);
this._snippetJavaScriptSourceForSnippetId[snippet.id] = snippetJavaScriptSource;
this._snippetScriptMapping._fireUISourceCodeAdded(snippetJavaScriptSource);
return snippetJavaScriptSource;
},
/**
* @param {WebInspector.SnippetJavaScriptSource} snippetJavaScriptSource
*/
deleteScriptSnippet: function(snippetJavaScriptSource)
{
var snippet = this._snippetStorage.snippetForId(snippetJavaScriptSource.snippetId);
this._snippetStorage.deleteSnippet(snippet);
this._removeBreakpoints(snippetJavaScriptSource);
this._releaseSnippetScript(snippetJavaScriptSource);
delete this._snippetJavaScriptSourceForSnippetId[snippet.id];
this._snippetScriptMapping._fireUISourceCodeRemoved(snippetJavaScriptSource);
},
/**
* @param {WebInspector.SnippetJavaScriptSource} snippetJavaScriptSource
* @param {string} newName
*/
renameScriptSnippet: function(snippetJavaScriptSource, newName)
{
var snippet = this._snippetStorage.snippetForId(snippetJavaScriptSource.snippetId);
if (!snippet || !newName || snippet.name === newName)
return;
snippet.name = newName;
snippetJavaScriptSource.urlChanged(snippet.name);
},
/**
* @param {WebInspector.SnippetJavaScriptSource} snippetJavaScriptSource
* @return {boolean}
*/
_isDivergedFromVM: function(snippetJavaScriptSource)
{
var script = this._scriptForUISourceCode.get(snippetJavaScriptSource);
return !script;
},
/**
* @param {WebInspector.SnippetJavaScriptSource} snippetJavaScriptSource
* @param {string} newContent
*/
_setScriptSnippetContent: function(snippetJavaScriptSource, newContent)
{
var snippet = this._snippetStorage.snippetForId(snippetJavaScriptSource.snippetId);
snippet.content = newContent;
snippetJavaScriptSource.contentChanged(newContent, "text/javascript");
},
/**
* @param {WebInspector.SnippetJavaScriptSource} snippetJavaScriptSource
*/
_scriptSnippetEdited: function(snippetJavaScriptSource)
{
var script = this._scriptForUISourceCode.get(snippetJavaScriptSource);
if (!script)
return;
var breakpointLocations = this._removeBreakpoints(snippetJavaScriptSource);
this._releaseSnippetScript(snippetJavaScriptSource);
this._restoreBreakpoints(snippetJavaScriptSource, breakpointLocations);
},
/**
* @param {string} snippetId
* @return {number}
*/
_nextEvaluationIndex: function(snippetId)
{
var evaluationIndex = this._lastSnippetEvaluationIndexSetting.get() + 1;
this._lastSnippetEvaluationIndexSetting.set(evaluationIndex);
return evaluationIndex;
},
/**
* @param {WebInspector.SnippetJavaScriptSource} snippetJavaScriptSource
*/
evaluateScriptSnippet: function(snippetJavaScriptSource)
{
var breakpointLocations = this._removeBreakpoints(snippetJavaScriptSource);
this._releaseSnippetScript(snippetJavaScriptSource);
this._restoreBreakpoints(snippetJavaScriptSource, breakpointLocations);
var evaluationIndex = this._nextEvaluationIndex(snippetJavaScriptSource.snippetId);
snippetJavaScriptSource._evaluationIndex = evaluationIndex;
var evaluationUrl = this._evaluationSourceURL(snippetJavaScriptSource);
var expression = snippetJavaScriptSource.workingCopy();
// In order to stop on the breakpoints during the snippet evaluation we need to compile and run it separately.
// If separate compilation and execution is not supported by the port we fall back to evaluation in console.
// In case we don't need that since debugger is already paused.
// We do the same when we are stopped on the call frame since debugger is already paused and can not stop on breakpoint anymore.
if (WebInspector.debuggerModel.selectedCallFrame() || !Capabilities.separateScriptCompilationAndExecutionEnabled) {
expression = snippetJavaScriptSource.workingCopy() + "\n//@ sourceURL=" + evaluationUrl + "\n";
WebInspector.evaluateInConsole(expression, true);
return;
}
WebInspector.showConsole();
DebuggerAgent.compileScript(expression, evaluationUrl, compileCallback.bind(this));
/**
* @param {?string} error
* @param {string=} scriptId
* @param {string=} syntaxErrorMessage
*/
function compileCallback(error, scriptId, syntaxErrorMessage)
{
if (!snippetJavaScriptSource || snippetJavaScriptSource._evaluationIndex !== evaluationIndex)
return;
if (error) {
console.error(error);
return;
}
if (!scriptId) {
var consoleMessage = WebInspector.ConsoleMessage.create(
WebInspector.ConsoleMessage.MessageSource.JS,
WebInspector.ConsoleMessage.MessageLevel.Error,
syntaxErrorMessage || "",
WebInspector.ConsoleMessage.MessageType.Log,
"", 0, 1, null, null, null);
WebInspector.console.addMessage(consoleMessage);
return;
}
var breakpointLocations = this._removeBreakpoints(snippetJavaScriptSource);
this._restoreBreakpoints(snippetJavaScriptSource, breakpointLocations);
WebInspector.consoleView.runScript(scriptId);
}
},
/**
* @param {WebInspector.DebuggerModel.Location} rawLocation
* @return {WebInspector.UILocation}
*/
_rawLocationToUILocation: function(rawLocation)
{
var uiSourceCode = this._uiSourceCodeForScriptId[rawLocation.scriptId];
return new WebInspector.UILocation(uiSourceCode, rawLocation.lineNumber, rawLocation.columnNumber || 0);
},
/**
* @param {WebInspector.UISourceCode} uiSourceCode
* @param {number} lineNumber
* @param {number} columnNumber
* @return {WebInspector.DebuggerModel.Location}
*/
_uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
{
var script = this._scriptForUISourceCode.get(uiSourceCode);
if (!script)
return null;
return WebInspector.debuggerModel.createRawLocation(script, lineNumber, columnNumber);
},
/**
* @return {Array.<WebInspector.UISourceCode>}
*/
_uiSourceCodes: function()
{
var result = this._releasedUISourceCodes();
for (var snippetId in this._snippetJavaScriptSourceForSnippetId)
result.push(this._snippetJavaScriptSourceForSnippetId[snippetId]);
return result;
},
/**
* @return {Array.<WebInspector.UISourceCode>}
*/
_releasedUISourceCodes: function()
{
var result = [];
for (var scriptId in this._uiSourceCodeForScriptId) {
var uiSourceCode = this._uiSourceCodeForScriptId[scriptId];
if (!uiSourceCode.isSnippet)
result.push(uiSourceCode);
}
return result;
},
/**
* @param {WebInspector.Script} script
*/
_addScript: function(script)
{
var snippetId = this._snippetIdForSourceURL(script.sourceURL);
var snippetJavaScriptSource = this._snippetJavaScriptSourceForSnippetId[snippetId];
if (!snippetJavaScriptSource || this._evaluationSourceURL(snippetJavaScriptSource) !== script.sourceURL) {
this._createUISourceCodeForScript(script);
return;
}
console.assert(!this._scriptForUISourceCode.get(snippetJavaScriptSource));
this._uiSourceCodeForScriptId[script.scriptId] = snippetJavaScriptSource;
this._scriptForUISourceCode.put(snippetJavaScriptSource, script);
script.setSourceMapping(this._snippetScriptMapping);
},
/**
* @param {WebInspector.Script} script
*/
_createUISourceCodeForScript: function(script)
{
var uiSourceCode = new WebInspector.JavaScriptSource(script.sourceURL, null, script, this._snippetScriptMapping, false);
uiSourceCode.isSnippetEvaluation = true;
this._uiSourceCodeForScriptId[script.scriptId] = uiSourceCode;
this._scriptForUISourceCode.put(uiSourceCode, script);
script.setSourceMapping(this._snippetScriptMapping);
},
/**
* @param {WebInspector.SnippetJavaScriptSource} snippetJavaScriptSource
* @return {Array.<Object>}
*/
_removeBreakpoints: function(snippetJavaScriptSource)
{
var breakpointLocations = WebInspector.breakpointManager.breakpointLocationsForUISourceCode(snippetJavaScriptSource);
for (var i = 0; i < breakpointLocations.length; ++i)
breakpointLocations[i].breakpoint.remove();
return breakpointLocations;
},
/**
* @param {WebInspector.SnippetJavaScriptSource} snippetJavaScriptSource
* @param {Array.<Object>} breakpointLocations
*/
_restoreBreakpoints: function(snippetJavaScriptSource, breakpointLocations)
{
for (var i = 0; i < breakpointLocations.length; ++i) {
var uiLocation = breakpointLocations[i].uiLocation;
var breakpoint = breakpointLocations[i].breakpoint;
WebInspector.breakpointManager.setBreakpoint(uiLocation.uiSourceCode, uiLocation.lineNumber, breakpoint.condition(), breakpoint.enabled());
}
},
/**
* @param {WebInspector.SnippetJavaScriptSource} snippetJavaScriptSource
*/
_releaseSnippetScript: function(snippetJavaScriptSource)
{
var script = this._scriptForUISourceCode.get(snippetJavaScriptSource);
if (!script)
return;
delete this._uiSourceCodeForScriptId[script.scriptId];
this._scriptForUISourceCode.remove(snippetJavaScriptSource);
delete snippetJavaScriptSource._evaluationIndex;
this._createUISourceCodeForScript(script);
},
/**
* @param {WebInspector.SnippetJavaScriptSource} snippetJavaScriptSource
* @return {string}
*/
_evaluationSourceURL: function(snippetJavaScriptSource)
{
var snippetPrefix = WebInspector.ScriptSnippetModel.snippetSourceURLPrefix;
var evaluationSuffix = "_" + snippetJavaScriptSource._evaluationIndex;
return snippetPrefix + snippetJavaScriptSource.snippetId + evaluationSuffix;
},
/**
* @param {string} sourceURL
* @return {string|null}
*/
_snippetIdForSourceURL: function(sourceURL)
{
var snippetPrefix = WebInspector.ScriptSnippetModel.snippetSourceURLPrefix;
if (!sourceURL.startsWith(snippetPrefix))
return null;
var splittedURL = sourceURL.substring(snippetPrefix.length).split("_");
var snippetId = splittedURL[0];
return snippetId;
},
_reset: function()
{
var removedUISourceCodes = this._releasedUISourceCodes();
this._uiSourceCodeForScriptId = {};
this._scriptForUISourceCode = new Map();
this._loadSnippets();
}
}
WebInspector.ScriptSnippetModel.prototype.__proto__ = WebInspector.Object.prototype;
/**
* @constructor
* @extends {WebInspector.JavaScriptSource}
* @param {string} snippetId
* @param {string} snippetName
* @param {WebInspector.ContentProvider} contentProvider
* @param {WebInspector.ScriptSnippetModel} scriptSnippetModel
*/
WebInspector.SnippetJavaScriptSource = function(snippetId, snippetName, contentProvider, scriptSnippetModel)
{
WebInspector.JavaScriptSource.call(this, snippetName, null, contentProvider, scriptSnippetModel.scriptMapping, true);
this._snippetId = snippetId;
this._scriptSnippetModel = scriptSnippetModel;
this.isSnippet = true;
}
WebInspector.SnippetJavaScriptSource.prototype = {
/**
* @return {boolean}
*/
isEditable: function()
{
return true;
},
/**
* @return {boolean}
*/
isDivergedFromVM: function()
{
return this._scriptSnippetModel._isDivergedFromVM(this);
},
/**
* @param {function(?string)} callback
*/
workingCopyCommitted: function(callback)
{
this._scriptSnippetModel._setScriptSnippetContent(this, this.workingCopy());
callback(null);
},
workingCopyChanged: function()
{
this._scriptSnippetModel._scriptSnippetEdited(this);
},
evaluate: function()
{
this._scriptSnippetModel.evaluateScriptSnippet(this);
},
/**
* @return {boolean}
*/
supportsEnabledBreakpointsWhileEditing: function()
{
return true;
},
/**
* @return {string}
*/
breakpointStorageId: function()
{
return WebInspector.ScriptSnippetModel.snippetSourceURLPrefix + this.snippetId;
},
/**
* @return {string}
*/
get snippetId()
{
return this._snippetId;
}
}
WebInspector.SnippetJavaScriptSource.prototype.__proto__ = WebInspector.JavaScriptSource.prototype;
/**
* @constructor
* @extends {WebInspector.Object}
* @implements {WebInspector.SourceMapping}
* @implements {WebInspector.UISourceCodeProvider}
* @param {WebInspector.ScriptSnippetModel} scriptSnippetModel
*/
WebInspector.SnippetScriptMapping = function(scriptSnippetModel)
{
this._scriptSnippetModel = scriptSnippetModel;
}
WebInspector.SnippetScriptMapping.prototype = {
/**
* @param {WebInspector.RawLocation} rawLocation
* @return {WebInspector.UILocation}
*/
rawLocationToUILocation: function(rawLocation)
{
var debuggerModelLocation = /** @type {WebInspector.DebuggerModel.Location} */ rawLocation;
return this._scriptSnippetModel._rawLocationToUILocation(debuggerModelLocation);
},
/**
* @param {WebInspector.UISourceCode} uiSourceCode
* @param {number} lineNumber
* @param {number} columnNumber
* @return {WebInspector.DebuggerModel.Location}
*/
uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
{
return this._scriptSnippetModel._uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber);
},
/**
* @return {Array.<WebInspector.UISourceCode>}
*/
uiSourceCodes: function()
{
return this._scriptSnippetModel._uiSourceCodes();
},
/**
* @param {string} sourceURL
* @return {string|null}
*/
snippetIdForSourceURL: function(sourceURL)
{
return this._scriptSnippetModel._snippetIdForSourceURL(sourceURL);
},
/**
* @param {WebInspector.Script} script
*/
addScript: function(script)
{
this._scriptSnippetModel._addScript(script);
},
/**
* @param {WebInspector.UISourceCode} uiSourceCode
*/
_fireUISourceCodeAdded: function(uiSourceCode)
{
this.dispatchEventToListeners(WebInspector.UISourceCodeProvider.Events.UISourceCodeAdded, uiSourceCode);
},
/**
* @param {WebInspector.UISourceCode} uiSourceCode
*/
_fireUISourceCodeRemoved: function(uiSourceCode)
{
this.dispatchEventToListeners(WebInspector.UISourceCodeProvider.Events.UISourceCodeRemoved, uiSourceCode);
},
reset: function()
{
this._scriptSnippetModel._reset();
}
}
WebInspector.SnippetScriptMapping.prototype.__proto__ = WebInspector.Object.prototype;
/**
* @constructor
* @extends {WebInspector.StaticContentProvider}
* @param {WebInspector.Snippet} snippet
*/
WebInspector.SnippetContentProvider = function(snippet)
{
WebInspector.StaticContentProvider.call(this, WebInspector.resourceTypes.Script, snippet.content);
}
WebInspector.SnippetContentProvider.prototype.__proto__ = WebInspector.StaticContentProvider.prototype;
/**
* @type {?WebInspector.ScriptSnippetModel}
*/
WebInspector.scriptSnippetModel = null;