Index: Source/devtools/front_end/bindings/SASSSourceMapping.js |
diff --git a/Source/devtools/front_end/bindings/SASSSourceMapping.js b/Source/devtools/front_end/bindings/SASSSourceMapping.js |
index e7d8fb597f8ba5971861c077d83d60838d464c3f..370fe3bdc50d1608ef166eed7b763a8964d963a0 100644 |
--- a/Source/devtools/front_end/bindings/SASSSourceMapping.js |
+++ b/Source/devtools/front_end/bindings/SASSSourceMapping.js |
@@ -38,12 +38,11 @@ |
*/ |
WebInspector.SASSSourceMapping = function(cssModel, workspace, networkMapping, networkProject) |
{ |
- this.pollPeriodMs = 30 * 1000; |
- this.pollIntervalMs = 200; |
this._cssModel = cssModel; |
this._workspace = workspace; |
this._networkProject = networkProject; |
this._addingRevisionCounter = 0; |
+ this._pollManager = new WebInspector.SASSSourceMapping.PollManager(this._cssModel, networkMapping, this._updateCSSRevision.bind(this)); |
this._reset(); |
WebInspector.fileManager.addEventListener(WebInspector.FileManager.EventTypes.SavedURL, this._fileSaveFinished, this); |
WebInspector.moduleSetting("cssSourceMapsEnabled").addChangeListener(this._toggleSourceMapSupport, this); |
@@ -94,531 +93,553 @@ WebInspector.SASSSourceMapping.prototype = { |
{ |
var sassURL = /** @type {string} */ (event.data); |
var cssURLs = this._sassURLToCSSURLs.get(sassURL).valuesArray(); |
- this._sassFileSaved(sassURL, cssURLs, false); |
+ this._pollManager.sassFileChanged(sassURL, cssURLs, false); |
}, |
/** |
- * @param {string} headerName |
- * @param {!Object.<string, string>} headers |
- * @return {?string} |
+ * @param {!WebInspector.UISourceCode} cssUISourceCode |
+ * @param {string} content |
+ * @return {boolean} |
*/ |
- _headerValue: function(headerName, headers) |
+ _updateCSSRevision: function(cssUISourceCode, content) |
{ |
- headerName = headerName.toLowerCase(); |
- var value = null; |
- for (var name in headers) { |
- if (name.toLowerCase() === headerName) { |
- value = headers[name]; |
- break; |
- } |
- } |
- return value; |
+ ++this._addingRevisionCounter; |
+ cssUISourceCode.addRevision(content); |
+ var cssURL = this._networkMapping.networkURL(cssUISourceCode); |
+ var completeSourceMapURL = this._completeSourceMapURLForCSSURL[cssURL]; |
+ if (!completeSourceMapURL) |
+ return false; |
+ var ids = this._cssModel.styleSheetIdsForURL(cssURL); |
+ if (!ids) |
+ return false; |
+ var headers = []; |
+ for (var i = 0; i < ids.length; ++i) |
+ headers.push(this._cssModel.styleSheetHeaderForId(ids[i])); |
+ this._loadSourceMapAndBindUISourceCode(headers, true, completeSourceMapURL); |
+ return true; |
}, |
/** |
- * @param {!Object.<string, string>} headers |
- * @return {?Date} |
+ * @param {!WebInspector.CSSStyleSheetHeader} header |
*/ |
- _lastModified: function(headers) |
+ addHeader: function(header) |
{ |
- var lastModifiedHeader = this._headerValue("last-modified", headers); |
- if (!lastModifiedHeader) |
- return null; |
- var lastModified = new Date(lastModifiedHeader); |
- if (isNaN(lastModified.getTime())) |
- return null; |
- return lastModified; |
+ if (!header.sourceMapURL || !header.sourceURL || !WebInspector.moduleSetting("cssSourceMapsEnabled").get()) |
+ return; |
+ var completeSourceMapURL = WebInspector.ParsedURL.completeURL(header.sourceURL, header.sourceMapURL); |
+ if (!completeSourceMapURL) |
+ return; |
+ this._completeSourceMapURLForCSSURL[header.sourceURL] = completeSourceMapURL; |
+ this._loadSourceMapAndBindUISourceCode([header], false, completeSourceMapURL); |
}, |
/** |
- * @param {!Object.<string, string>} headers |
- * @param {string} url |
- * @return {?Date} |
+ * @param {!WebInspector.CSSStyleSheetHeader} header |
*/ |
- _checkLastModified: function(headers, url) |
+ removeHeader: function(header) |
{ |
- var lastModified = this._lastModified(headers); |
- if (lastModified) |
- return lastModified; |
+ var sourceURL = header.sourceURL; |
+ if (!sourceURL || !header.sourceMapURL || !this._completeSourceMapURLForCSSURL[sourceURL]) |
+ return; |
+ var sourceMap = this._sourceMapByStyleSheetURL[sourceURL]; |
+ var sources = sourceMap.sources(); |
+ for (var i = 0; i < sources.length; ++i) |
+ this._sassURLToCSSURLs.remove(sources[i], sourceURL); |
+ delete this._sourceMapByStyleSheetURL[sourceURL]; |
+ delete this._completeSourceMapURLForCSSURL[sourceURL]; |
- var etagMessage = this._headerValue("etag", headers) ? ", \"ETag\" response header found instead" : ""; |
- var message = String.sprintf("The \"Last-Modified\" response header is missing or invalid for %s%s. The CSS auto-reload functionality will not work correctly.", url, etagMessage); |
- WebInspector.console.log(message); |
- return null; |
+ var completeSourceMapURL = WebInspector.ParsedURL.completeURL(sourceURL, header.sourceMapURL); |
+ if (completeSourceMapURL) |
+ delete this._sourceMapByURL[completeSourceMapURL]; |
+ WebInspector.cssWorkspaceBinding.updateLocations(header); |
}, |
/** |
- * @param {string} sassURL |
- * @param {!Array.<string>} cssURLs |
- * @param {boolean} wasLoadedFromFileSystem |
+ * @param {!Array.<!WebInspector.CSSStyleSheetHeader>} headersWithSameSourceURL |
+ * @param {boolean} forceRebind |
+ * @param {string} completeSourceMapURL |
*/ |
- _sassFileSaved: function(sassURL, cssURLs, wasLoadedFromFileSystem) |
+ _loadSourceMapAndBindUISourceCode: function(headersWithSameSourceURL, forceRebind, completeSourceMapURL) |
{ |
- if (!cssURLs) |
- return; |
- if (!WebInspector.moduleSetting("cssReloadEnabled").get()) |
- return; |
- |
- var sassFile = this._networkMapping.uiSourceCodeForURL(sassURL, this._cssModel.target()); |
- console.assert(sassFile); |
- if (wasLoadedFromFileSystem) |
- sassFile.requestMetadata(metadataReceived.bind(this)); |
- else |
- WebInspector.ResourceLoader.loadUsingTargetUA(sassURL, null, sassLoadedViaNetwork.bind(this)); |
+ console.assert(headersWithSameSourceURL.length); |
+ var sourceURL = headersWithSameSourceURL[0].sourceURL; |
+ this._loadSourceMapForStyleSheet(completeSourceMapURL, sourceURL, forceRebind, sourceMapLoaded.bind(this)); |
/** |
- * @param {number} statusCode |
- * @param {!Object.<string, string>} headers |
- * @param {string} content |
+ * @param {?WebInspector.SourceMap} sourceMap |
* @this {WebInspector.SASSSourceMapping} |
*/ |
- function sassLoadedViaNetwork(statusCode, headers, content) |
+ function sourceMapLoaded(sourceMap) |
{ |
- if (statusCode >= 400) { |
- console.error("Could not load content for " + sassURL + " : " + "HTTP status code: " + statusCode); |
+ if (!sourceMap) |
return; |
+ |
+ this._sourceMapByStyleSheetURL[sourceURL] = sourceMap; |
+ for (var i = 0; i < headersWithSameSourceURL.length; ++i) { |
+ if (forceRebind) |
+ WebInspector.cssWorkspaceBinding.updateLocations(headersWithSameSourceURL[i]); |
+ else |
+ this._bindUISourceCode(headersWithSameSourceURL[i], sourceMap); |
} |
- var lastModified = this._checkLastModified(headers, sassURL); |
- if (!lastModified) |
- return; |
- metadataReceived.call(this, lastModified); |
} |
+ }, |
+ |
+ /** |
+ * @param {string} completeSourceMapURL |
+ * @param {string} completeStyleSheetURL |
+ * @param {boolean} forceReload |
+ * @param {function(?WebInspector.SourceMap)} callback |
+ */ |
+ _loadSourceMapForStyleSheet: function(completeSourceMapURL, completeStyleSheetURL, forceReload, callback) |
+ { |
+ var sourceMap = this._sourceMapByURL[completeSourceMapURL]; |
+ if (sourceMap && !forceReload) { |
+ callback(sourceMap); |
+ return; |
+ } |
+ |
+ var pendingCallbacks = this._pendingSourceMapLoadingCallbacks[completeSourceMapURL]; |
+ if (pendingCallbacks) { |
+ pendingCallbacks.push(callback); |
+ return; |
+ } |
+ |
+ pendingCallbacks = [callback]; |
+ this._pendingSourceMapLoadingCallbacks[completeSourceMapURL] = pendingCallbacks; |
+ |
+ WebInspector.SourceMap.load(completeSourceMapURL, completeStyleSheetURL, sourceMapLoaded.bind(this)); |
/** |
- * @param {?Date} timestamp |
+ * @param {?WebInspector.SourceMap} sourceMap |
* @this {WebInspector.SASSSourceMapping} |
*/ |
- function metadataReceived(timestamp) |
+ function sourceMapLoaded(sourceMap) |
{ |
- if (!timestamp) |
+ var callbacks = this._pendingSourceMapLoadingCallbacks[completeSourceMapURL]; |
+ delete this._pendingSourceMapLoadingCallbacks[completeSourceMapURL]; |
+ if (!callbacks) |
return; |
- |
- var now = Date.now(); |
- var deadlineMs = now + this.pollPeriodMs; |
- var pollData = this._pollDataForSASSURL[sassURL]; |
- if (pollData) { |
- var dataByURL = pollData.dataByURL; |
- for (var url in dataByURL) |
- clearTimeout(dataByURL[url].timer); |
- } |
- pollData = { dataByURL: {}, deadlineMs: deadlineMs, sassTimestamp: timestamp }; |
- this._pollDataForSASSURL[sassURL] = pollData; |
- for (var i = 0; i < cssURLs.length; ++i) { |
- pollData.dataByURL[cssURLs[i]] = { previousPoll: now }; |
- this._pollCallback(cssURLs[i], sassURL); |
- } |
+ if (sourceMap) |
+ this._sourceMapByURL[completeSourceMapURL] = sourceMap; |
+ else |
+ delete this._sourceMapByURL[completeSourceMapURL]; |
+ for (var i = 0; i < callbacks.length; ++i) |
+ callbacks[i](sourceMap); |
} |
}, |
/** |
- * @param {string} cssURL |
- * @param {string} sassURL |
+ * @param {!WebInspector.CSSStyleSheetHeader} header |
+ * @param {!WebInspector.SourceMap} sourceMap |
*/ |
- _pollCallback: function(cssURL, sassURL) |
+ _bindUISourceCode: function(header, sourceMap) |
{ |
- var now; |
- var pollData = this._pollDataForSASSURL[sassURL]; |
- if (!pollData) |
- return; |
- |
- if ((now = new Date().getTime()) > pollData.deadlineMs) { |
- WebInspector.console.warn(WebInspector.UIString("%s hasn't been updated in %d seconds.", cssURL, this.pollPeriodMs / 1000)); |
- this._stopPolling(cssURL, sassURL); |
- return; |
+ WebInspector.cssWorkspaceBinding.pushSourceMapping(header, this); |
+ var cssURL = header.sourceURL; |
+ var sources = sourceMap.sources(); |
+ for (var i = 0; i < sources.length; ++i) { |
+ var sassURL = sources[i]; |
+ this._sassURLToCSSURLs.set(sassURL, cssURL); |
+ if (!this._networkMapping.hasMappingForURL(sassURL) && !this._networkMapping.uiSourceCodeForURL(sassURL, header.target())) { |
+ var contentProvider = sourceMap.sourceContentProvider(sassURL, WebInspector.resourceTypes.Stylesheet); |
+ this._networkProject.addFileForURL(sassURL, contentProvider); |
+ } |
} |
- var nextPoll = this.pollIntervalMs + pollData.dataByURL[cssURL].previousPoll; |
- var remainingTimeoutMs = Math.max(0, nextPoll - now); |
- pollData.dataByURL[cssURL].previousPoll = now + remainingTimeoutMs; |
- pollData.dataByURL[cssURL].timer = setTimeout(this._reloadCSS.bind(this, cssURL, sassURL), remainingTimeoutMs); |
}, |
/** |
- * @param {string} cssURL |
- * @param {string} sassURL |
+ * @override |
+ * @param {!WebInspector.CSSLocation} rawLocation |
+ * @return {?WebInspector.UILocation} |
*/ |
- _stopPolling: function(cssURL, sassURL) |
+ rawLocationToUILocation: function(rawLocation) |
{ |
- var pollData = this._pollDataForSASSURL[sassURL]; |
- if (!pollData) |
- return; |
- delete pollData.dataByURL[cssURL]; |
- if (!Object.keys(pollData.dataByURL).length) |
- delete this._pollDataForSASSURL[sassURL]; |
+ var sourceMap = this._sourceMapByStyleSheetURL[rawLocation.url]; |
+ if (!sourceMap) |
+ return null; |
+ var entry = sourceMap.findEntry(rawLocation.lineNumber, rawLocation.columnNumber); |
+ if (!entry || !entry.sourceURL) |
+ return null; |
+ var uiSourceCode = this._networkMapping.uiSourceCodeForURL(entry.sourceURL, rawLocation.target()); |
+ if (!uiSourceCode) |
+ return null; |
+ return uiSourceCode.uiLocation(entry.sourceLineNumber, entry.sourceColumnNumber); |
}, |
/** |
- * @param {string} cssURL |
- * @param {string} sassURL |
+ * @override |
+ * @param {!WebInspector.UISourceCode} uiSourceCode |
+ * @param {number} lineNumber |
+ * @param {number} columnNumber |
+ * @return {?WebInspector.CSSLocation} |
*/ |
- _reloadCSS: function(cssURL, sassURL) |
+ uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) |
{ |
- var cssUISourceCode = this._networkMapping.uiSourceCodeForURL(cssURL, this._cssModel.target()); |
- if (!cssUISourceCode) { |
- WebInspector.console.warn(WebInspector.UIString("%s resource missing. Please reload the page.", cssURL)); |
- this._stopPolling(cssURL, sassURL) |
- return; |
- } |
- |
- if (this._networkMapping.hasMappingForURL(sassURL)) |
- this._reloadCSSFromFileSystem(cssUISourceCode, sassURL); |
- else |
- this._reloadCSSFromNetwork(cssUISourceCode, sassURL); |
+ return null; |
}, |
/** |
- * @param {!WebInspector.UISourceCode} cssUISourceCode |
- * @param {string} sassURL |
+ * @override |
+ * @return {boolean} |
*/ |
- _reloadCSSFromNetwork: function(cssUISourceCode, sassURL) |
+ isIdentity: function() |
{ |
- var cssURL = this._networkMapping.networkURL(cssUISourceCode); |
- var data = this._pollDataForSASSURL[sassURL]; |
- if (!data) { |
- this._stopPolling(cssURL, sassURL); |
- return; |
- } |
- var headers = { "if-modified-since": new Date(data.sassTimestamp.getTime() - 1000).toUTCString() }; |
- WebInspector.ResourceLoader.loadUsingTargetUA(cssURL, headers, contentLoaded.bind(this)); |
- |
- /** |
- * @param {number} statusCode |
- * @param {!Object.<string, string>} headers |
- * @param {string} content |
- * @this {WebInspector.SASSSourceMapping} |
- */ |
- function contentLoaded(statusCode, headers, content) |
- { |
- if (statusCode >= 400) { |
- console.error("Could not load content for " + cssURL + " : " + "HTTP status code: " + statusCode); |
- this._stopPolling(cssURL, sassURL); |
- return; |
- } |
- if (!this._pollDataForSASSURL[sassURL]) { |
- this._stopPolling(cssURL, sassURL); |
- return; |
- } |
- if (statusCode === 304) { |
- this._pollCallback(cssURL, sassURL); |
- return; |
- } |
- var lastModified = this._checkLastModified(headers, cssURL); |
- if (!lastModified) { |
- this._stopPolling(cssURL, sassURL); |
- return; |
- } |
- if (lastModified.getTime() < data.sassTimestamp.getTime()) { |
- this._pollCallback(cssURL, sassURL); |
- return; |
- } |
- if (this._updateCSSRevision(cssUISourceCode, content, sassURL)) |
- this._stopPolling(cssURL, sassURL); |
- } |
- }, |
- |
- /** |
- * @param {!WebInspector.UISourceCode} cssUISourceCode |
- * @param {string} sassURL |
- */ |
- _reloadCSSFromFileSystem: function(cssUISourceCode, sassURL) |
- { |
- cssUISourceCode.requestMetadata(metadataCallback.bind(this)); |
- |
- /** |
- * @param {?Date} timestamp |
- * @this {WebInspector.SASSSourceMapping} |
- */ |
- function metadataCallback(timestamp) |
- { |
- var cssURL = this._networkMapping.networkURL(cssUISourceCode); |
- if (!timestamp) { |
- this._pollCallback(cssURL, sassURL); |
- return; |
- } |
- var cssTimestamp = timestamp.getTime(); |
- var pollData = this._pollDataForSASSURL[sassURL]; |
- if (!pollData) { |
- this._stopPolling(cssURL, sassURL); |
- return; |
- } |
- |
- if (cssTimestamp < pollData.sassTimestamp.getTime()) { |
- this._pollCallback(cssURL, sassURL); |
- return; |
- } |
- |
- cssUISourceCode.requestOriginalContent(contentCallback.bind(this)); |
- |
- /** |
- * @param {?string} content |
- * @this {WebInspector.SASSSourceMapping} |
- */ |
- function contentCallback(content) |
- { |
- // Empty string is a valid value, null means error. |
- if (content === null) |
- return; |
- if (this._updateCSSRevision(cssUISourceCode, content, sassURL)) |
- this._stopPolling(cssURL, sassURL); |
- } |
- } |
+ return false; |
}, |
/** |
- * @param {!WebInspector.UISourceCode} cssUISourceCode |
- * @param {string} content |
- * @param {string} sassURL |
+ * @override |
+ * @param {!WebInspector.UISourceCode} uiSourceCode |
+ * @param {number} lineNumber |
* @return {boolean} |
*/ |
- _updateCSSRevision: function(cssUISourceCode, content, sassURL) |
+ uiLineHasMapping: function(uiSourceCode, lineNumber) |
{ |
- ++this._addingRevisionCounter; |
- cssUISourceCode.addRevision(content); |
- var cssURL = this._networkMapping.networkURL(cssUISourceCode); |
- var completeSourceMapURL = this._completeSourceMapURLForCSSURL[cssURL]; |
- if (!completeSourceMapURL) |
- return false; |
- var ids = this._cssModel.styleSheetIdsForURL(cssURL); |
- if (!ids) |
- return false; |
- var headers = []; |
- for (var i = 0; i < ids.length; ++i) |
- headers.push(this._cssModel.styleSheetHeaderForId(ids[i])); |
- this._loadSourceMapAndBindUISourceCode(headers, true, completeSourceMapURL); |
return true; |
}, |
/** |
- * @param {!WebInspector.CSSStyleSheetHeader} header |
+ * @return {!WebInspector.Target} |
*/ |
- addHeader: function(header) |
+ target: function() |
{ |
- if (!header.sourceMapURL || !header.sourceURL || !WebInspector.moduleSetting("cssSourceMapsEnabled").get()) |
- return; |
- var completeSourceMapURL = WebInspector.ParsedURL.completeURL(header.sourceURL, header.sourceMapURL); |
- if (!completeSourceMapURL) |
- return; |
- this._completeSourceMapURLForCSSURL[header.sourceURL] = completeSourceMapURL; |
- this._loadSourceMapAndBindUISourceCode([header], false, completeSourceMapURL); |
+ return this._cssModel.target(); |
}, |
/** |
- * @param {!WebInspector.CSSStyleSheetHeader} header |
+ * @param {!WebInspector.Event} event |
*/ |
- removeHeader: function(header) |
+ _uiSourceCodeAdded: function(event) |
{ |
- var sourceURL = header.sourceURL; |
- if (!sourceURL || !header.sourceMapURL || !this._completeSourceMapURLForCSSURL[sourceURL]) |
+ var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); |
+ var networkURL = this._networkMapping.networkURL(uiSourceCode); |
+ var cssURLs = this._sassURLToCSSURLs.get(networkURL).valuesArray(); |
+ if (!cssURLs) |
return; |
- var sourceMap = this._sourceMapByStyleSheetURL[sourceURL]; |
- var sources = sourceMap.sources(); |
- for (var i = 0; i < sources.length; ++i) |
- this._sassURLToCSSURLs.remove(sources[i], sourceURL); |
- delete this._sourceMapByStyleSheetURL[sourceURL]; |
- delete this._completeSourceMapURLForCSSURL[sourceURL]; |
- |
- var completeSourceMapURL = WebInspector.ParsedURL.completeURL(sourceURL, header.sourceMapURL); |
- if (completeSourceMapURL) |
- delete this._sourceMapByURL[completeSourceMapURL]; |
- WebInspector.cssWorkspaceBinding.updateLocations(header); |
- }, |
- |
- /** |
- * @param {!Array.<!WebInspector.CSSStyleSheetHeader>} headersWithSameSourceURL |
- * @param {boolean} forceRebind |
- * @param {string} completeSourceMapURL |
- */ |
- _loadSourceMapAndBindUISourceCode: function(headersWithSameSourceURL, forceRebind, completeSourceMapURL) |
- { |
- console.assert(headersWithSameSourceURL.length); |
- var sourceURL = headersWithSameSourceURL[0].sourceURL; |
- this._loadSourceMapForStyleSheet(completeSourceMapURL, sourceURL, forceRebind, sourceMapLoaded.bind(this)); |
- |
- /** |
- * @param {?WebInspector.SourceMap} sourceMap |
- * @this {WebInspector.SASSSourceMapping} |
- */ |
- function sourceMapLoaded(sourceMap) |
- { |
- if (!sourceMap) |
- return; |
- |
- this._sourceMapByStyleSheetURL[sourceURL] = sourceMap; |
- for (var i = 0; i < headersWithSameSourceURL.length; ++i) { |
- if (forceRebind) |
- WebInspector.cssWorkspaceBinding.updateLocations(headersWithSameSourceURL[i]); |
- else |
- this._bindUISourceCode(headersWithSameSourceURL[i], sourceMap); |
+ for (var i = 0; i < cssURLs.length; ++i) { |
+ var ids = this._cssModel.styleSheetIdsForURL(cssURLs[i]); |
+ for (var j = 0; j < ids.length; ++j) { |
+ var header = this._cssModel.styleSheetHeaderForId(ids[j]); |
+ console.assert(header); |
+ WebInspector.cssWorkspaceBinding.updateLocations(/** @type {!WebInspector.CSSStyleSheetHeader} */ (header)); |
} |
} |
}, |
/** |
- * @param {string} completeSourceMapURL |
- * @param {string} completeStyleSheetURL |
- * @param {boolean} forceReload |
- * @param {function(?WebInspector.SourceMap)} callback |
+ * @param {!WebInspector.Event} event |
*/ |
- _loadSourceMapForStyleSheet: function(completeSourceMapURL, completeStyleSheetURL, forceReload, callback) |
+ _uiSourceCodeContentCommitted: function(event) |
{ |
- var sourceMap = this._sourceMapByURL[completeSourceMapURL]; |
- if (sourceMap && !forceReload) { |
- callback(sourceMap); |
- return; |
- } |
- |
- var pendingCallbacks = this._pendingSourceMapLoadingCallbacks[completeSourceMapURL]; |
- if (pendingCallbacks) { |
- pendingCallbacks.push(callback); |
- return; |
+ var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.uiSourceCode); |
+ if (uiSourceCode.project().type() === WebInspector.projectTypes.FileSystem) { |
+ var networkURL = this._networkMapping.networkURL(uiSourceCode); |
+ var cssURLs = this._sassURLToCSSURLs.get(networkURL).valuesArray(); |
+ this._pollManager.sassFileChanged(networkURL, cssURLs, true); |
} |
+ }, |
- pendingCallbacks = [callback]; |
- this._pendingSourceMapLoadingCallbacks[completeSourceMapURL] = pendingCallbacks; |
+ _reset: function() |
+ { |
+ this._addingRevisionCounter = 0; |
+ this._completeSourceMapURLForCSSURL = {}; |
+ /** @type {!Multimap<string, string>} */ |
+ this._sassURLToCSSURLs = new Multimap(); |
+ /** @type {!Object.<string, !Array.<function(?WebInspector.SourceMap)>>} */ |
+ this._pendingSourceMapLoadingCallbacks = {}; |
+ /** @type {!Object.<string, !WebInspector.SourceMap>} */ |
+ this._sourceMapByURL = {}; |
+ this._sourceMapByStyleSheetURL = {}; |
+ this._pollManager.reset(); |
+ } |
+} |
- WebInspector.SourceMap.load(completeSourceMapURL, completeStyleSheetURL, sourceMapLoaded.bind(this)); |
+/** |
+ * @constructor |
+ * @param {!WebInspector.CSSStyleModel} cssModel |
+ * @param {!WebInspector.NetworkMapping} networkMapping |
+ * @param {function(!WebInspector.UISourceCode, string):boolean} callback |
+ */ |
+WebInspector.SASSSourceMapping.PollManager = function(cssModel, networkMapping, callback) |
+{ |
+ this.pollPeriodMs = 30 * 1000; |
+ this.pollIntervalMs = 200; |
+ this._networkMapping = networkMapping; |
+ this._callback = callback; |
+ this._cssModel = cssModel; |
+ this.reset(); |
+} |
- /** |
- * @param {?WebInspector.SourceMap} sourceMap |
- * @this {WebInspector.SASSSourceMapping} |
- */ |
- function sourceMapLoaded(sourceMap) |
- { |
- var callbacks = this._pendingSourceMapLoadingCallbacks[completeSourceMapURL]; |
- delete this._pendingSourceMapLoadingCallbacks[completeSourceMapURL]; |
- if (!callbacks) |
- return; |
- if (sourceMap) |
- this._sourceMapByURL[completeSourceMapURL] = sourceMap; |
- else |
- delete this._sourceMapByURL[completeSourceMapURL]; |
- for (var i = 0; i < callbacks.length; ++i) |
- callbacks[i](sourceMap); |
- } |
+WebInspector.SASSSourceMapping.PollManager.prototype = { |
+ reset: function() |
+ { |
+ /** @type {!Object.<string, !{deadlineMs: number, dataByURL: !Object.<string, !{timer: number, previousPoll: number}>}>} */ |
+ this._pollDataForSASSURL = {}; |
}, |
/** |
- * @param {!WebInspector.CSSStyleSheetHeader} header |
- * @param {!WebInspector.SourceMap} sourceMap |
+ * @param {string} headerName |
+ * @param {!Object.<string, string>} headers |
+ * @return {?string} |
*/ |
- _bindUISourceCode: function(header, sourceMap) |
+ _headerValue: function(headerName, headers) |
{ |
- WebInspector.cssWorkspaceBinding.pushSourceMapping(header, this); |
- var cssURL = header.sourceURL; |
- var sources = sourceMap.sources(); |
- for (var i = 0; i < sources.length; ++i) { |
- var sassURL = sources[i]; |
- this._sassURLToCSSURLs.set(sassURL, cssURL); |
- if (!this._networkMapping.hasMappingForURL(sassURL) && !this._networkMapping.uiSourceCodeForURL(sassURL, header.target())) { |
- var contentProvider = sourceMap.sourceContentProvider(sassURL, WebInspector.resourceTypes.Stylesheet); |
- this._networkProject.addFileForURL(sassURL, contentProvider); |
+ headerName = headerName.toLowerCase(); |
+ var value = null; |
+ for (var name in headers) { |
+ if (name.toLowerCase() === headerName) { |
+ value = headers[name]; |
+ break; |
} |
} |
+ return value; |
}, |
/** |
- * @override |
- * @param {!WebInspector.CSSLocation} rawLocation |
- * @return {?WebInspector.UILocation} |
+ * @param {!Object.<string, string>} headers |
+ * @return {?Date} |
*/ |
- rawLocationToUILocation: function(rawLocation) |
+ _lastModified: function(headers) |
{ |
- var sourceMap = this._sourceMapByStyleSheetURL[rawLocation.url]; |
- if (!sourceMap) |
- return null; |
- var entry = sourceMap.findEntry(rawLocation.lineNumber, rawLocation.columnNumber); |
- if (!entry || !entry.sourceURL) |
+ var lastModifiedHeader = this._headerValue("last-modified", headers); |
+ if (!lastModifiedHeader) |
return null; |
- var uiSourceCode = this._networkMapping.uiSourceCodeForURL(entry.sourceURL, rawLocation.target()); |
- if (!uiSourceCode) |
+ var lastModified = new Date(lastModifiedHeader); |
+ if (isNaN(lastModified.getTime())) |
return null; |
- return uiSourceCode.uiLocation(entry.sourceLineNumber, entry.sourceColumnNumber); |
+ return lastModified; |
}, |
/** |
- * @override |
- * @param {!WebInspector.UISourceCode} uiSourceCode |
- * @param {number} lineNumber |
- * @param {number} columnNumber |
- * @return {?WebInspector.CSSLocation} |
+ * @param {!Object.<string, string>} headers |
+ * @param {string} url |
+ * @return {?Date} |
*/ |
- uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) |
+ _checkLastModified: function(headers, url) |
{ |
+ var lastModified = this._lastModified(headers); |
+ if (lastModified) |
+ return lastModified; |
+ |
+ var etagMessage = this._headerValue("etag", headers) ? ", \"ETag\" response header found instead" : ""; |
+ var message = String.sprintf("The \"Last-Modified\" response header is missing or invalid for %s%s. The CSS auto-reload functionality will not work correctly.", url, etagMessage); |
+ WebInspector.console.log(message); |
return null; |
}, |
/** |
- * @override |
- * @return {boolean} |
+ * @param {string} sassURL |
+ * @param {!Array.<string>} cssURLs |
+ * @param {boolean} wasLoadedFromFileSystem |
*/ |
- isIdentity: function() |
+ sassFileChanged: function(sassURL, cssURLs, wasLoadedFromFileSystem) |
{ |
- return false; |
+ if (!cssURLs) |
+ return; |
+ if (!WebInspector.moduleSetting("cssReloadEnabled").get()) |
+ return; |
+ |
+ var sassFile = this._networkMapping.uiSourceCodeForURL(sassURL, this._cssModel.target()); |
+ console.assert(sassFile); |
+ if (wasLoadedFromFileSystem) |
+ sassFile.requestMetadata(metadataReceived.bind(this)); |
+ else |
+ WebInspector.ResourceLoader.loadUsingTargetUA(sassURL, null, sassLoadedViaNetwork.bind(this)); |
+ |
+ /** |
+ * @param {number} statusCode |
+ * @param {!Object.<string, string>} headers |
+ * @param {string} content |
+ * @this {WebInspector.SASSSourceMapping.PollManager} |
+ */ |
+ function sassLoadedViaNetwork(statusCode, headers, content) |
+ { |
+ if (statusCode >= 400) { |
+ console.error("Could not load content for " + sassURL + " : " + "HTTP status code: " + statusCode); |
+ return; |
+ } |
+ var lastModified = this._checkLastModified(headers, sassURL); |
+ if (!lastModified) |
+ return; |
+ metadataReceived.call(this, lastModified); |
+ } |
+ |
+ /** |
+ * @param {?Date} timestamp |
+ * @this {WebInspector.SASSSourceMapping.PollManager} |
+ */ |
+ function metadataReceived(timestamp) |
+ { |
+ if (!timestamp) |
+ return; |
+ |
+ var now = Date.now(); |
+ var deadlineMs = now + this.pollPeriodMs; |
+ var pollData = this._pollDataForSASSURL[sassURL]; |
+ if (pollData) { |
+ var dataByURL = pollData.dataByURL; |
+ for (var url in dataByURL) |
+ clearTimeout(dataByURL[url].timer); |
+ } |
+ pollData = { dataByURL: {}, deadlineMs: deadlineMs, sassTimestamp: timestamp }; |
+ this._pollDataForSASSURL[sassURL] = pollData; |
+ for (var i = 0; i < cssURLs.length; ++i) { |
+ pollData.dataByURL[cssURLs[i]] = { previousPoll: now }; |
+ this._pollCallback(cssURLs[i], sassURL); |
+ } |
+ } |
}, |
/** |
- * @override |
- * @param {!WebInspector.UISourceCode} uiSourceCode |
- * @param {number} lineNumber |
- * @return {boolean} |
+ * @param {string} cssURL |
+ * @param {string} sassURL |
*/ |
- uiLineHasMapping: function(uiSourceCode, lineNumber) |
+ _pollCallback: function(cssURL, sassURL) |
{ |
- return true; |
+ var now; |
+ var pollData = this._pollDataForSASSURL[sassURL]; |
+ if (!pollData) |
+ return; |
+ |
+ if ((now = new Date().getTime()) > pollData.deadlineMs) { |
+ WebInspector.console.warn(WebInspector.UIString("%s hasn't been updated in %d seconds.", cssURL, this.pollPeriodMs / 1000)); |
+ this._stopPolling(cssURL, sassURL); |
+ return; |
+ } |
+ var nextPoll = this.pollIntervalMs + pollData.dataByURL[cssURL].previousPoll; |
+ var remainingTimeoutMs = Math.max(0, nextPoll - now); |
+ pollData.dataByURL[cssURL].previousPoll = now + remainingTimeoutMs; |
+ pollData.dataByURL[cssURL].timer = setTimeout(this._reloadCSS.bind(this, cssURL, sassURL), remainingTimeoutMs); |
}, |
/** |
- * @return {!WebInspector.Target} |
+ * @param {string} cssURL |
+ * @param {string} sassURL |
*/ |
- target: function() |
+ _stopPolling: function(cssURL, sassURL) |
{ |
- return this._cssModel.target(); |
+ var pollData = this._pollDataForSASSURL[sassURL]; |
+ if (!pollData) |
+ return; |
+ delete pollData.dataByURL[cssURL]; |
+ if (!Object.keys(pollData.dataByURL).length) |
+ delete this._pollDataForSASSURL[sassURL]; |
}, |
/** |
- * @param {!WebInspector.Event} event |
+ * @param {string} cssURL |
+ * @param {string} sassURL |
*/ |
- _uiSourceCodeAdded: function(event) |
+ _reloadCSS: function(cssURL, sassURL) |
{ |
- var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); |
- var networkURL = this._networkMapping.networkURL(uiSourceCode); |
- var cssURLs = this._sassURLToCSSURLs.get(networkURL).valuesArray(); |
- if (!cssURLs) |
+ var cssUISourceCode = this._networkMapping.uiSourceCodeForURL(cssURL, this._cssModel.target()); |
+ if (!cssUISourceCode) { |
+ WebInspector.console.warn(WebInspector.UIString("%s resource missing. Please reload the page.", cssURL)); |
+ this._stopPolling(cssURL, sassURL) |
return; |
- for (var i = 0; i < cssURLs.length; ++i) { |
- var ids = this._cssModel.styleSheetIdsForURL(cssURLs[i]); |
- for (var j = 0; j < ids.length; ++j) { |
- var header = this._cssModel.styleSheetHeaderForId(ids[j]); |
- console.assert(header); |
- WebInspector.cssWorkspaceBinding.updateLocations(/** @type {!WebInspector.CSSStyleSheetHeader} */ (header)); |
- } |
} |
+ |
+ if (this._networkMapping.hasMappingForURL(sassURL)) |
+ this._reloadCSSFromFileSystem(cssUISourceCode, sassURL); |
+ else |
+ this._reloadCSSFromNetwork(cssUISourceCode, sassURL); |
}, |
/** |
- * @param {!WebInspector.Event} event |
+ * @param {!WebInspector.UISourceCode} cssUISourceCode |
+ * @param {string} sassURL |
*/ |
- _uiSourceCodeContentCommitted: function(event) |
+ _reloadCSSFromNetwork: function(cssUISourceCode, sassURL) |
{ |
- var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.uiSourceCode); |
- if (uiSourceCode.project().type() === WebInspector.projectTypes.FileSystem) { |
- var networkURL = this._networkMapping.networkURL(uiSourceCode); |
- var cssURLs = this._sassURLToCSSURLs.get(networkURL).valuesArray(); |
- this._sassFileSaved(networkURL, cssURLs, true); |
+ var cssURL = this._networkMapping.networkURL(cssUISourceCode); |
+ var data = this._pollDataForSASSURL[sassURL]; |
+ if (!data) { |
+ this._stopPolling(cssURL, sassURL); |
+ return; |
+ } |
+ var headers = { "if-modified-since": new Date(data.sassTimestamp.getTime() - 1000).toUTCString() }; |
+ WebInspector.ResourceLoader.loadUsingTargetUA(cssURL, headers, contentLoaded.bind(this)); |
+ |
+ /** |
+ * @param {number} statusCode |
+ * @param {!Object.<string, string>} headers |
+ * @param {string} content |
+ * @this {WebInspector.SASSSourceMapping.PollManager} |
+ */ |
+ function contentLoaded(statusCode, headers, content) |
+ { |
+ if (statusCode >= 400) { |
+ console.error("Could not load content for " + cssURL + " : " + "HTTP status code: " + statusCode); |
+ this._stopPolling(cssURL, sassURL); |
+ return; |
+ } |
+ if (!this._pollDataForSASSURL[sassURL]) { |
+ this._stopPolling(cssURL, sassURL); |
+ return; |
+ } |
+ if (statusCode === 304) { |
+ this._pollCallback(cssURL, sassURL); |
+ return; |
+ } |
+ var lastModified = this._checkLastModified(headers, cssURL); |
+ if (!lastModified) { |
+ this._stopPolling(cssURL, sassURL); |
+ return; |
+ } |
+ if (lastModified.getTime() < data.sassTimestamp.getTime()) { |
+ this._pollCallback(cssURL, sassURL); |
+ return; |
+ } |
+ if (this._callback(cssUISourceCode, content)) |
+ this._stopPolling(cssURL, sassURL); |
} |
}, |
- _reset: function() |
+ /** |
+ * @param {!WebInspector.UISourceCode} cssUISourceCode |
+ * @param {string} sassURL |
+ */ |
+ _reloadCSSFromFileSystem: function(cssUISourceCode, sassURL) |
{ |
- this._addingRevisionCounter = 0; |
- this._completeSourceMapURLForCSSURL = {}; |
- /** @type {!Multimap<string, string>} */ |
- this._sassURLToCSSURLs = new Multimap(); |
- /** @type {!Object.<string, !Array.<function(?WebInspector.SourceMap)>>} */ |
- this._pendingSourceMapLoadingCallbacks = {}; |
- /** @type {!Object.<string, !{deadlineMs: number, dataByURL: !Object.<string, !{timer: number, previousPoll: number}>}>} */ |
- this._pollDataForSASSURL = {}; |
- /** @type {!Object.<string, !WebInspector.SourceMap>} */ |
- this._sourceMapByURL = {}; |
- this._sourceMapByStyleSheetURL = {}; |
+ cssUISourceCode.requestMetadata(metadataCallback.bind(this)); |
+ |
+ /** |
+ * @param {?Date} timestamp |
+ * @this {WebInspector.SASSSourceMapping.PollManager} |
+ */ |
+ function metadataCallback(timestamp) |
+ { |
+ var cssURL = this._networkMapping.networkURL(cssUISourceCode); |
+ if (!timestamp) { |
+ this._pollCallback(cssURL, sassURL); |
+ return; |
+ } |
+ var cssTimestamp = timestamp.getTime(); |
+ var pollData = this._pollDataForSASSURL[sassURL]; |
+ if (!pollData) { |
+ this._stopPolling(cssURL, sassURL); |
+ return; |
+ } |
+ |
+ if (cssTimestamp < pollData.sassTimestamp.getTime()) { |
+ this._pollCallback(cssURL, sassURL); |
+ return; |
+ } |
+ |
+ cssUISourceCode.requestOriginalContent(contentCallback.bind(this)); |
+ |
+ /** |
+ * @param {?string} content |
+ * @this {WebInspector.SASSSourceMapping.PollManager} |
+ */ |
+ function contentCallback(content) |
+ { |
+ // Empty string is a valid value, null means error. |
+ if (content === null) |
+ return; |
+ if (this._callback(cssUISourceCode, content)) |
+ this._stopPolling(cssURL, sassURL); |
+ } |
+ } |
} |
-} |
+} |