blob: 98a51465172c1e426a466475247f2fa2cb8146a3 [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.
*/
/**
* @constructor
* @extends {WebInspector.Object}
* @implements {WebInspector.ContentProvider}
* @param {WebInspector.Workspace} workspace
* @param {string} url
* @param {WebInspector.ResourceType} contentType
* @param {boolean} isEditable
*/
WebInspector.UISourceCode = function(workspace, url, contentType, isEditable)
{
this._workspace = workspace;
this._url = url;
this._parsedURL = new WebInspector.ParsedURL(url);
this._contentType = contentType;
this._isEditable = isEditable;
/**
* @type Array.<function(?string,boolean,string)>
*/
this._requestContentCallbacks = [];
/**
* @type Array.<WebInspector.LiveLocation>
*/
this._liveLocations = [];
/**
* @type {Array.<WebInspector.PresentationConsoleMessage>}
*/
this._consoleMessages = [];
/**
* @type {Array.<WebInspector.Revision>}
*/
this.history = [];
if (this.isEditable() && this._url)
this._restoreRevisionHistory();
this._formatterMapping = new WebInspector.IdentityFormatterSourceMapping();
}
WebInspector.UISourceCode.Events = {
FormattedChanged: "FormattedChanged",
WorkingCopyChanged: "WorkingCopyChanged",
WorkingCopyCommitted: "WorkingCopyCommitted",
TitleChanged: "TitleChanged",
ConsoleMessageAdded: "ConsoleMessageAdded",
ConsoleMessageRemoved: "ConsoleMessageRemoved",
ConsoleMessagesCleared: "ConsoleMessagesCleared",
SourceMappingChanged: "SourceMappingChanged",
}
WebInspector.UISourceCode.prototype = {
/**
* @return {string}
*/
get url()
{
return this._url;
},
/**
* @param {string} url
*/
urlChanged: function(url)
{
this._url = url;
this._parsedURL = new WebInspector.ParsedURL(this._url);
this.dispatchEventToListeners(WebInspector.UISourceCode.Events.TitleChanged, null);
},
/**
* @return {WebInspector.ParsedURL}
*/
get parsedURL()
{
return this._parsedURL;
},
/**
* @return {string}
*/
contentURL: function()
{
return this._url;
},
/**
* @return {WebInspector.ResourceType}
*/
contentType: function()
{
return this._contentType;
},
/**
* @return {WebInspector.ScriptFile}
*/
scriptFile: function()
{
return this._scriptFile;
},
/**
* @param {WebInspector.ScriptFile} scriptFile
*/
setScriptFile: function(scriptFile)
{
this._scriptFile = scriptFile;
},
/**
* @return {WebInspector.StyleFile}
*/
styleFile: function()
{
return this._styleFile;
},
/**
* @param {WebInspector.StyleFile} styleFile
*/
setStyleFile: function(styleFile)
{
this._styleFile = styleFile;
},
/**
* @param {function(?string,boolean,string)} callback
*/
requestContent: function(callback)
{
if (this._content || this._contentLoaded) {
callback(this._content, false, this._mimeType);
return;
}
this._requestContentCallbacks.push(callback);
if (this._requestContentCallbacks.length === 1)
this._workspace.requestFileContent(this, this._fireContentAvailable.bind(this));
},
/**
* @param {function(?string,boolean,string)} callback
*/
requestOriginalContent: function(callback)
{
this._workspace.requestFileContent(this, callback);
},
/**
* @param {string} content
*/
_commitContent: function(content)
{
this._content = content;
this._contentLoaded = true;
var lastRevision = this.history.length ? this.history[this.history.length - 1] : null;
if (!lastRevision || lastRevision._content !== this._content) {
var revision = new WebInspector.Revision(this, this._content, new Date());
this.history.push(revision);
revision._persist();
}
var oldWorkingCopy = this._workingCopy;
delete this._workingCopy;
this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyCommitted, {oldWorkingCopy: oldWorkingCopy, workingCopy: this.workingCopy()});
this._workspace.dispatchEventToListeners(WebInspector.Workspace.Events.UISourceCodeContentCommitted, { uiSourceCode: this, content: this._content });
if (this._url && WebInspector.fileManager.isURLSaved(this._url)) {
WebInspector.fileManager.save(this._url, this._content, false);
WebInspector.fileManager.close(this._url);
}
},
/**
* @param {string} content
*/
addRevision: function(content)
{
this._commitContent(content);
},
_restoreRevisionHistory: function()
{
if (!window.localStorage)
return;
var registry = WebInspector.Revision._revisionHistoryRegistry();
var historyItems = registry[this.url];
if (!historyItems || !historyItems.length)
return;
for (var i = 0; i < historyItems.length; ++i) {
var content = window.localStorage[historyItems[i].key];
var timestamp = new Date(historyItems[i].timestamp);
var revision = new WebInspector.Revision(this, content, timestamp);
this.history.push(revision);
}
this._content = this.history[this.history.length - 1].content;
this._contentLoaded = true;
this._mimeType = this.canonicalMimeType();
},
_clearRevisionHistory: function()
{
if (!window.localStorage)
return;
var registry = WebInspector.Revision._revisionHistoryRegistry();
var historyItems = registry[this.url];
for (var i = 0; historyItems && i < historyItems.length; ++i)
delete window.localStorage[historyItems[i].key];
delete registry[this.url];
window.localStorage["revision-history"] = JSON.stringify(registry);
},
revertToOriginal: function()
{
/**
* @this {WebInspector.UISourceCode}
* @param {?string} content
* @param {boolean} contentEncoded
* @param {string} mimeType
*/
function callback(content, contentEncoded, mimeType)
{
if (typeof content !== "string")
return;
this.addRevision(content);
}
this.requestOriginalContent(callback.bind(this));
},
/**
* @param {function(WebInspector.UISourceCode)} callback
*/
revertAndClearHistory: function(callback)
{
/**
* @this {WebInspector.UISourceCode}
* @param {?string} content
* @param {boolean} contentEncoded
* @param {string} mimeType
*/
function revert(content, contentEncoded, mimeType)
{
if (typeof content !== "string")
return;
this.addRevision(content);
this._clearRevisionHistory();
this.history = [];
callback(this);
}
this.requestOriginalContent(revert.bind(this));
},
/**
* @return {boolean}
*/
isEditable: function()
{
return this._isEditable;
},
/**
* @return {string}
*/
workingCopy: function()
{
if (this.isDirty())
return this._workingCopy;
return this._content;
},
/**
* @param {string} newWorkingCopy
*/
setWorkingCopy: function(newWorkingCopy)
{
var wasDirty = this.isDirty();
this._mimeType = this.canonicalMimeType();
var oldWorkingCopy = this._workingCopy;
if (this._content === newWorkingCopy)
delete this._workingCopy;
else
this._workingCopy = newWorkingCopy;
this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyChanged, {oldWorkingCopy: oldWorkingCopy, workingCopy: this.workingCopy(), wasDirty: wasDirty});
},
/**
* @param {function(?string)} callback
*/
commitWorkingCopy: function(callback)
{
if (!this.isDirty()) {
callback(null);
return;
}
this._commitContent(this._workingCopy);
callback(null);
},
/**
* @return {boolean}
*/
isDirty: function()
{
return typeof this._workingCopy !== "undefined" && this._workingCopy !== this._content;
},
/**
* @return {string}
*/
mimeType: function()
{
return this._mimeType;
},
/**
* @return {string}
*/
canonicalMimeType: function()
{
return this.contentType().canonicalMimeType() || this._mimeType;
},
/**
* @return {?string}
*/
content: function()
{
return this._content;
},
/**
* @param {string} query
* @param {boolean} caseSensitive
* @param {boolean} isRegex
* @param {function(Array.<WebInspector.ContentProvider.SearchMatch>)} callback
*/
searchInContent: function(query, caseSensitive, isRegex, callback)
{
var content = this.content();
if (content) {
var provider = new WebInspector.StaticContentProvider(this.contentType(), content);
provider.searchInContent(query, caseSensitive, isRegex, callback);
return;
}
this._workspace.searchInFileContent(this, query, caseSensitive, isRegex, callback);
},
/**
* @param {?string} content
* @param {boolean} contentEncoded
* @param {string} mimeType
*/
_fireContentAvailable: function(content, contentEncoded, mimeType)
{
this._contentLoaded = true;
this._mimeType = mimeType;
this._content = content;
var callbacks = this._requestContentCallbacks.slice();
this._requestContentCallbacks = [];
for (var i = 0; i < callbacks.length; ++i)
callbacks[i](content, contentEncoded, mimeType);
if (this._formatOnLoad) {
delete this._formatOnLoad;
this.setFormatted(true);
}
},
/**
* @return {boolean}
*/
contentLoaded: function()
{
return this._contentLoaded;
},
/**
* @param {number} lineNumber
* @param {number} columnNumber
* @return {WebInspector.RawLocation}
*/
uiLocationToRawLocation: function(lineNumber, columnNumber)
{
if (!this._sourceMapping)
return null;
var location = this._formatterMapping.formattedToOriginal(lineNumber, columnNumber);
return this._sourceMapping.uiLocationToRawLocation(this, location[0], location[1]);
},
/**
* @param {WebInspector.LiveLocation} liveLocation
*/
addLiveLocation: function(liveLocation)
{
this._liveLocations.push(liveLocation);
},
/**
* @param {WebInspector.LiveLocation} liveLocation
*/
removeLiveLocation: function(liveLocation)
{
this._liveLocations.remove(liveLocation);
},
updateLiveLocations: function()
{
var locationsCopy = this._liveLocations.slice();
for (var i = 0; i < locationsCopy.length; ++i)
locationsCopy[i].update();
},
/**
* @param {WebInspector.UILocation} uiLocation
*/
overrideLocation: function(uiLocation)
{
var location = this._formatterMapping.originalToFormatted(uiLocation.lineNumber, uiLocation.columnNumber);
uiLocation.lineNumber = location[0];
uiLocation.columnNumber = location[1];
return uiLocation;
},
/**
* @return {Array.<WebInspector.PresentationConsoleMessage>}
*/
consoleMessages: function()
{
return this._consoleMessages;
},
/**
* @param {WebInspector.PresentationConsoleMessage} message
*/
consoleMessageAdded: function(message)
{
this._consoleMessages.push(message);
this.dispatchEventToListeners(WebInspector.UISourceCode.Events.ConsoleMessageAdded, message);
},
/**
* @param {WebInspector.PresentationConsoleMessage} message
*/
consoleMessageRemoved: function(message)
{
this._consoleMessages.remove(message);
this.dispatchEventToListeners(WebInspector.UISourceCode.Events.ConsoleMessageRemoved, message);
},
consoleMessagesCleared: function()
{
this._consoleMessages = [];
this.dispatchEventToListeners(WebInspector.UISourceCode.Events.ConsoleMessagesCleared);
},
/**
* @return {boolean}
*/
formatted: function()
{
return !!this._formatted;
},
/**
* @param {boolean} formatted
*/
setFormatted: function(formatted)
{
if (!this.contentLoaded()) {
this._formatOnLoad = formatted;
return;
}
if (this._formatted === formatted)
return;
this._formatted = formatted;
// Re-request content
this._contentLoaded = false;
this._content = false;
WebInspector.UISourceCode.prototype.requestContent.call(this, didGetContent.bind(this));
/**
* @this {WebInspector.UISourceCode}
* @param {?string} content
* @param {boolean} contentEncoded
* @param {string} mimeType
*/
function didGetContent(content, contentEncoded, mimeType)
{
var formatter;
if (!formatted)
formatter = new WebInspector.IdentityFormatter();
else
formatter = WebInspector.Formatter.createFormatter(this.contentType());
formatter.formatContent(mimeType, content || "", formattedChanged.bind(this));
/**
* @this {WebInspector.UISourceCode}
* @param {string} content
* @param {WebInspector.FormatterSourceMapping} formatterMapping
*/
function formattedChanged(content, formatterMapping)
{
this._content = content;
delete this._workingCopy;
this._formatterMapping = formatterMapping;
this.dispatchEventToListeners(WebInspector.UISourceCode.Events.FormattedChanged, {content: content});
this.updateLiveLocations();
}
}
},
/**
* @return {WebInspector.Formatter} formatter
*/
createFormatter: function()
{
// overridden by subclasses.
return null;
},
/**
* @param {WebInspector.SourceMapping} sourceMapping
*/
setSourceMapping: function(sourceMapping)
{
this._sourceMapping = sourceMapping;
this.dispatchEventToListeners(WebInspector.UISourceCode.Events.SourceMappingChanged, null);
},
__proto__: WebInspector.Object.prototype
}
/**
* @interface
*/
WebInspector.UISourceCodeProvider = function()
{
}
WebInspector.UISourceCodeProvider.Events = {
UISourceCodeAdded: "UISourceCodeAdded",
TemporaryUISourceCodeAdded: "TemporaryUISourceCodeAdded",
TemporaryUISourceCodeRemoved: "TemporaryUISourceCodeRemoved",
UISourceCodeRemoved: "UISourceCodeRemoved"
}
WebInspector.UISourceCodeProvider.prototype = {
/**
* @return {Array.<WebInspector.UISourceCode>}
*/
uiSourceCodes: function() {},
/**
* @param {string} eventType
* @param {function(WebInspector.Event)} listener
* @param {Object=} thisObject
*/
addEventListener: function(eventType, listener, thisObject) { },
/**
* @param {string} eventType
* @param {function(WebInspector.Event)} listener
* @param {Object=} thisObject
*/
removeEventListener: function(eventType, listener, thisObject) { }
}
/**
* @constructor
* @param {WebInspector.UISourceCode} uiSourceCode
* @param {number} lineNumber
* @param {number} columnNumber
*/
WebInspector.UILocation = function(uiSourceCode, lineNumber, columnNumber)
{
this.uiSourceCode = uiSourceCode;
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
}
WebInspector.UILocation.prototype = {
/**
* @return {WebInspector.RawLocation}
*/
uiLocationToRawLocation: function()
{
return this.uiSourceCode.uiLocationToRawLocation(this.lineNumber, this.columnNumber);
},
/**
* @return {?string}
*/
url: function()
{
return this.uiSourceCode.contentURL();
}
}
/**
* @interface
*/
WebInspector.RawLocation = function()
{
}
/**
* @constructor
* @param {WebInspector.RawLocation} rawLocation
* @param {function(WebInspector.UILocation):(boolean|undefined)} updateDelegate
*/
WebInspector.LiveLocation = function(rawLocation, updateDelegate)
{
this._rawLocation = rawLocation;
this._updateDelegate = updateDelegate;
this._uiSourceCodes = [];
}
WebInspector.LiveLocation.prototype = {
update: function()
{
var uiLocation = this.uiLocation();
if (uiLocation) {
var uiSourceCode = uiLocation.uiSourceCode;
if (this._uiSourceCodes.indexOf(uiSourceCode) === -1) {
uiSourceCode.addLiveLocation(this);
this._uiSourceCodes.push(uiSourceCode);
}
var oneTime = this._updateDelegate(uiLocation);
if (oneTime)
this.dispose();
}
},
/**
* @return {WebInspector.RawLocation}
*/
rawLocation: function()
{
return this._rawLocation;
},
/**
* @return {WebInspector.UILocation}
*/
uiLocation: function()
{
// Should be overridden by subclasses.
},
dispose: function()
{
for (var i = 0; i < this._uiSourceCodes.length; ++i)
this._uiSourceCodes[i].removeLiveLocation(this);
this._uiSourceCodes = [];
}
}
/**
* @constructor
* @implements {WebInspector.ContentProvider}
* @param {WebInspector.UISourceCode} uiSourceCode
* @param {?string|undefined} content
* @param {Date} timestamp
*/
WebInspector.Revision = function(uiSourceCode, content, timestamp)
{
this._uiSourceCode = uiSourceCode;
this._content = content;
this._timestamp = timestamp;
}
WebInspector.Revision._revisionHistoryRegistry = function()
{
if (!WebInspector.Revision._revisionHistoryRegistryObject) {
if (window.localStorage) {
var revisionHistory = window.localStorage["revision-history"];
try {
WebInspector.Revision._revisionHistoryRegistryObject = revisionHistory ? JSON.parse(revisionHistory) : {};
} catch (e) {
WebInspector.Revision._revisionHistoryRegistryObject = {};
}
} else
WebInspector.Revision._revisionHistoryRegistryObject = {};
}
return WebInspector.Revision._revisionHistoryRegistryObject;
}
WebInspector.Revision.filterOutStaleRevisions = function()
{
if (!window.localStorage)
return;
var registry = WebInspector.Revision._revisionHistoryRegistry();
var filteredRegistry = {};
for (var url in registry) {
var historyItems = registry[url];
var filteredHistoryItems = [];
for (var i = 0; historyItems && i < historyItems.length; ++i) {
var historyItem = historyItems[i];
if (historyItem.loaderId === WebInspector.resourceTreeModel.mainFrame.loaderId) {
filteredHistoryItems.push(historyItem);
filteredRegistry[url] = filteredHistoryItems;
} else
delete window.localStorage[historyItem.key];
}
}
WebInspector.Revision._revisionHistoryRegistryObject = filteredRegistry;
function persist()
{
window.localStorage["revision-history"] = JSON.stringify(filteredRegistry);
}
// Schedule async storage.
setTimeout(persist, 0);
}
WebInspector.Revision.prototype = {
/**
* @return {WebInspector.UISourceCode}
*/
get uiSourceCode()
{
return this._uiSourceCode;
},
/**
* @return {Date}
*/
get timestamp()
{
return this._timestamp;
},
/**
* @return {?string}
*/
get content()
{
return this._content || null;
},
revertToThis: function()
{
function revert(content)
{
if (this._uiSourceCode._content !== content)
this._uiSourceCode.addRevision(content);
}
this.requestContent(revert.bind(this));
},
/**
* @return {string}
*/
contentURL: function()
{
return this._uiSourceCode.url;
},
/**
* @return {WebInspector.ResourceType}
*/
contentType: function()
{
return this._uiSourceCode.contentType();
},
/**
* @param {function(?string, boolean, string)} callback
*/
requestContent: function(callback)
{
callback(this._content || "", false, this.uiSourceCode.canonicalMimeType());
},
/**
* @param {string} query
* @param {boolean} caseSensitive
* @param {boolean} isRegex
* @param {function(Array.<WebInspector.ContentProvider.SearchMatch>)} callback
*/
searchInContent: function(query, caseSensitive, isRegex, callback)
{
callback([]);
},
_persist: function()
{
if (!window.localStorage)
return;
var url = this.contentURL();
if (!url || url.startsWith("inspector://"))
return;
var loaderId = WebInspector.resourceTreeModel.mainFrame.loaderId;
var timestamp = this.timestamp.getTime();
var key = "revision-history|" + url + "|" + loaderId + "|" + timestamp;
var registry = WebInspector.Revision._revisionHistoryRegistry();
var historyItems = registry[url];
if (!historyItems) {
historyItems = [];
registry[url] = historyItems;
}
historyItems.push({url: url, loaderId: loaderId, timestamp: timestamp, key: key});
function persist()
{
window.localStorage[key] = this._content;
window.localStorage["revision-history"] = JSON.stringify(registry);
}
// Schedule async storage.
setTimeout(persist.bind(this), 0);
}
}