Index: third_party/WebKit/Source/devtools/front_end/sass/SASSWorkspaceAdapter.js |
diff --git a/third_party/WebKit/Source/devtools/front_end/sass/SASSWorkspaceAdapter.js b/third_party/WebKit/Source/devtools/front_end/sass/SASSWorkspaceAdapter.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5dc3292aa35d1589bcca604fc88f81ca8b5cd3c7 |
--- /dev/null |
+++ b/third_party/WebKit/Source/devtools/front_end/sass/SASSWorkspaceAdapter.js |
@@ -0,0 +1,461 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+/** |
+ * @constructor |
+ * @param {!WebInspector.CSSStyleModel} cssModel |
+ * @param {!WebInspector.Workspace} workspace |
+ * @param {!WebInspector.NetworkMapping} networkMapping |
+ */ |
+WebInspector.SASSWorkspaceAdapter = function(cssModel, workspace, networkMapping) |
+{ |
+ this._workspace = workspace; |
+ this._networkMapping = networkMapping; |
+ this._cssModel = cssModel; |
+ |
+ /** @type {!Map<string, number>} */ |
+ this._versions = new Map(); |
+ /** @type {!Map<string, !Promise<boolean>>} */ |
+ this._awaitingPromises = new Map(); |
+ /** @type {!Map<string, function(boolean)>} */ |
+ this._awaitingFulfills = new Map(); |
+ |
+ /** @type {!Multimap<string, !WebInspector.SourceMapTracker>} */ |
+ this._urlToTrackers = new Multimap(); |
+ /** @type {!Set<string>} */ |
+ this._cssURLs = new Set(); |
+ |
+ this._eventListeners = [ |
+ this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this), |
+ this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this), |
+ this._workspace.addEventListener(WebInspector.Workspace.Events.WorkingCopyChanged, this._uiSourceCodeChanged, this), |
+ this._workspace.addEventListener(WebInspector.Workspace.Events.WorkingCopyCommitted, this._uiSourceCodeChanged, this), |
+ this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this), |
+ this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this), |
+ this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetChanged, this) |
+ ]; |
+} |
+ |
+/** |
+ * @constructor |
+ * @param {string} url |
+ * @param {number} version |
+ * @param {string} text |
+ */ |
+WebInspector.SASSWorkspaceAdapter.ContentResponse = function(url, version, text) |
+{ |
+ this.url = url; |
+ this.version = version; |
+ this.text = text; |
+} |
+ |
+WebInspector.SASSWorkspaceAdapter.prototype = { |
+ /** |
+ * @param {!WebInspector.SourceMap} sourceMap |
+ * @return {!WebInspector.SourceMapTracker} |
+ */ |
+ trackSources: function(sourceMap) |
+ { |
+ var cssURL = sourceMap.compiledURL(); |
+ this._cssURLs.add(cssURL); |
+ |
+ var allSources = new Set(sourceMap.sources().concat(cssURL)); |
+ for (var sourceURL of allSources) { |
+ if (this._versions.has(sourceURL)) |
+ continue; |
+ this._versions.set(sourceURL, 1); |
+ var promise = new Promise(fulfill => this._awaitingFulfills.set(sourceURL, fulfill)); |
+ this._awaitingPromises.set(sourceURL, promise); |
+ var contentProvider = sourceURL === cssURL ? this._headersForURL(sourceURL).peekLast() : this._sassUISourceCode(sourceURL); |
+ if (contentProvider) |
+ this._contentProviderAdded(sourceURL); |
+ } |
+ |
+ var tracker = new WebInspector.SourceMapTracker(this, sourceMap); |
+ for (var sourceURL of tracker.allURLs()) |
+ this._urlToTrackers.set(sourceURL, tracker); |
+ return tracker; |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.SourceMapTracker} tracker |
+ */ |
+ _stopTrackSources: function(tracker) |
+ { |
+ for (var sourceURL of tracker.allURLs()) { |
+ this._urlToTrackers.remove(sourceURL, tracker); |
+ if (!this._urlToTrackers.has(sourceURL)) { |
+ this._awaitingFulfills.get(sourceURL).call(null, false); |
+ this._awaitingFulfills.delete(sourceURL); |
+ this._awaitingPromises.delete(sourceURL); |
+ this._versions.delete(sourceURL); |
+ this._cssURLs.delete(sourceURL); |
+ } |
+ } |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ * @return {?WebInspector.UISourceCode} |
+ */ |
+ _sassUISourceCode: function(url) |
+ { |
+ return this._networkMapping.uiSourceCodeForURLForAnyTarget(url); |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ * @return {!Array<!WebInspector.CSSStyleSheetHeader>} |
+ */ |
+ _headersForURL: function(url) |
+ { |
+ return this._cssModel.styleSheetIdsForURL(url) |
+ .map(styleSheetId => this._cssModel.styleSheetHeaderForId(styleSheetId)); |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ */ |
+ _contentProviderAdded: function(url) |
+ { |
+ this._awaitingFulfills.get(url).call(null, true); |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ */ |
+ _contentProviderRemoved: function(url) |
+ { |
+ var trackers = new Set(this._urlToTrackers.get(url)); |
+ for (var tracker of trackers) |
+ tracker.dispose(); |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ * @return {boolean} |
+ */ |
+ _isSASSURL: function(url) |
+ { |
+ return this._versions.has(url) && !this._cssURLs.has(url); |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.Event} event |
+ */ |
+ _uiSourceCodeAdded: function(event) |
+ { |
+ var uiSourceCode = /** @type {!WebInspector.UISourceCode} */(event.data); |
+ var url = this._networkMapping.networkURL(uiSourceCode); |
+ if (!this._isSASSURL(url)) |
+ return; |
+ this._contentProviderAdded(url); |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.Event} event |
+ */ |
+ _uiSourceCodeRemoved: function(event) |
+ { |
+ var uiSourceCode = /** @type {!WebInspector.UISourceCode} */(event.data); |
+ var url = this._networkMapping.networkURL(uiSourceCode); |
+ if (!this._isSASSURL(url)) |
+ return; |
+ this._contentProviderRemoved(url); |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.Event} event |
+ */ |
+ _styleSheetAdded: function(event) |
+ { |
+ var styleSheetHeader = /** @type {!WebInspector.CSSStyleSheetHeader} */(event.data); |
+ var url = styleSheetHeader.sourceURL; |
+ if (!this._cssURLs.has(url)) |
+ return; |
+ this._contentProviderAdded(url); |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.Event} event |
+ */ |
+ _styleSheetRemoved: function(event) |
+ { |
+ var styleSheetHeader = /** @type {!WebInspector.CSSStyleSheetHeader} */(event.data); |
+ var url = styleSheetHeader.sourceURL; |
+ if (!this._cssURLs.has(url)) |
+ return; |
+ var headers = this._headersForURL(url); |
+ if (headers.length) |
+ return; |
+ this._contentProviderRemoved(url); |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.Event} event |
+ */ |
+ _uiSourceCodeChanged: function(event) |
+ { |
+ var uiSourceCode = /** @type {!WebInspector.UISourceCode} */(event.data.uiSourceCode); |
+ var url = this._networkMapping.networkURL(uiSourceCode); |
+ if (!this._isSASSURL(url)) |
+ return; |
+ this._newContentAvailable(url); |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.Event} event |
+ */ |
+ _styleSheetChanged: function(event) |
+ { |
+ var styleSheetId = /** @type {!CSSAgent.StyleSheetId} */(event.data.styleSheetId); |
+ var styleSheetHeader = this._cssModel.styleSheetHeaderForId(styleSheetId); |
+ var url = styleSheetHeader.sourceURL; |
+ if (!this._cssURLs.has(url)) |
+ return; |
+ this._newContentAvailable(url); |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ */ |
+ _newContentAvailable: function(url) |
+ { |
+ console.assert(this._versions.has(url), "The '" + url + "' is not tracked.") |
+ var newVersion = this._versions.get(url) + 1; |
+ this._versions.set(url, newVersion); |
+ for (var tracker of this._urlToTrackers.get(url)) |
+ tracker._newContentAvailable(url, newVersion); |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ * @return {number} |
+ */ |
+ _urlVersion: function(url) |
+ { |
+ var version = this._versions.get(url); |
+ console.assert(version, "The '" + url + "' is not tracked.") |
+ return version || 0; |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ * @return {!Promise<?WebInspector.SASSWorkspaceAdapter.ContentResponse>} |
+ */ |
+ _getContent: function(url) |
+ { |
+ console.assert(this._awaitingPromises.has(url), "The '" + url + "' is not tracked.") |
+ return this._awaitingPromises.get(url) |
+ .then(onContentProviderResolved.bind(this)); |
+ |
+ /** |
+ * @param {boolean} success |
+ * @return {!Promise<?WebInspector.SASSWorkspaceAdapter.ContentResponse>} |
+ * @this {WebInspector.SASSWorkspaceAdapter} |
+ */ |
+ function onContentProviderResolved(success) |
+ { |
+ if (!success) |
+ return Promise.resolve(/** @type {?WebInspector.SASSWorkspaceAdapter.ContentResponse} */(null)); |
+ var contentProvider = this._cssURLs.has(url) ? this._headersForURL(url).peekLast() : this._sassUISourceCode(url); |
+ if (!contentProvider) |
+ return Promise.resolve(/** @type {?WebInspector.SASSWorkspaceAdapter.ContentResponse} */(null)); |
+ return contentProvider.requestContent() |
+ .then(text => new WebInspector.SASSWorkspaceAdapter.ContentResponse(url, /** @type {number} */(this._versions.get(url)), text || "")); |
+ } |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ * @param {string} text |
+ * @return {?WebInspector.SASSWorkspaceAdapter.ContentResponse} |
+ */ |
+ _setSASSText: function(url, text) |
+ { |
+ console.assert(this._isSASSURL(url), "The url '" + url + "' should be a tracked SASS url"); |
+ var uiSourceCode = this._sassUISourceCode(url); |
+ if (!uiSourceCode) |
+ return null; |
+ setImmediate(() => uiSourceCode.addRevision(text)); |
+ var futureVersion = this._versions.get(url) + 1; |
+ return new WebInspector.SASSWorkspaceAdapter.ContentResponse(url, futureVersion, text); |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ * @param {string} text |
+ * @param {!Array<!WebInspector.SourceEdit>} cssEdits |
+ * @return {?WebInspector.SASSWorkspaceAdapter.ContentResponse} |
+ */ |
+ _setCSSText: function(url, text, cssEdits) |
+ { |
+ console.assert(this._cssURLs.has(url), "The url '" + url + "' should be a tracked CSS url"); |
+ var headers = this._headersForURL(url); |
+ if (!headers.length) |
+ return null; |
+ for (var i = 0; i < headers.length; ++i) |
+ this._cssModel.setStyleSheetText(headers[i].id, text, true); |
+ for (var i = cssEdits.length - 1; i >= 0; --i) { |
+ var edit = cssEdits[i]; |
+ var oldRange = edit.oldRange; |
+ var newRange = edit.newRange(); |
+ for (var j = 0; j < headers.length; ++j) { |
+ this._cssModel.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.ExternalRangeEdit, { |
+ styleSheetId: headers[j].id, |
+ oldRange: oldRange, |
+ newRange: newRange |
+ }); |
+ } |
+ } |
+ var futureVersion = this._versions.get(url) + headers.length; |
+ return new WebInspector.SASSWorkspaceAdapter.ContentResponse(url, futureVersion, text); |
+ } |
+} |
+ |
+/** |
+ * @constructor |
+ * @extends {WebInspector.Object} |
+ * @param {!WebInspector.SASSWorkspaceAdapter} adapter |
+ * @param {!WebInspector.SourceMap} sourceMap |
+ */ |
+WebInspector.SourceMapTracker = function(adapter, sourceMap) |
+{ |
+ WebInspector.Object.call(this); |
+ this._adapter = adapter; |
+ this._sourceMap = sourceMap; |
+ this._cssURL = sourceMap.compiledURL(); |
+ this._sassURLs = sourceMap.sources().slice(); |
+ this._allURLs = this._sassURLs.concat(this._cssURL); |
+ this._terminated = false; |
+ this._versions = new Map(); |
+ for (var url of this._allURLs) |
+ this._versions.set(url, adapter._urlVersion(url)); |
+} |
+ |
+/** @enum {string} */ |
+WebInspector.SourceMapTracker.Events = { |
+ SourceChanged: "SourceChanged", |
+ TrackingStopped: "TrackingStopped" |
+} |
+ |
+WebInspector.SourceMapTracker.prototype = { |
+ /** |
+ * @return {!WebInspector.SourceMap} |
+ */ |
+ sourceMap: function() |
+ { |
+ return this._sourceMap; |
+ }, |
+ |
+ /** |
+ * @return {!Array<string>} |
+ */ |
+ allURLs: function() |
+ { |
+ return this._allURLs; |
+ }, |
+ |
+ /** |
+ * @return {string} |
+ */ |
+ cssURL: function() |
+ { |
+ return this._cssURL; |
+ }, |
+ |
+ /** |
+ * @return {!Array<string>} |
+ */ |
+ sassURLs: function() |
+ { |
+ return this._sassURLs; |
+ }, |
+ |
+ /** |
+ * @return {boolean} |
+ */ |
+ isOutdated: function() |
+ { |
+ if (this._terminated) |
+ return true; |
+ for (var url of this._allURLs) { |
+ if (this._adapter._urlVersion(url) > this._versions.get(url)) |
+ return true; |
+ } |
+ return false; |
+ }, |
+ |
+ /** |
+ * @param {string} text |
+ * @param {!Array<!WebInspector.SourceEdit>} edits |
+ * @return {boolean} |
+ */ |
+ setCSSText: function(text, edits) |
+ { |
+ if (this._terminated || this.isOutdated()) |
+ return false; |
+ var result = this._adapter._setCSSText(this._cssURL, text, edits); |
+ this._handleContentResponse(result); |
+ return !!result; |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ * @param {string} text |
+ * @return {boolean} |
+ */ |
+ setSASSText: function(url, text) |
+ { |
+ if (this._terminated || this.isOutdated()) |
+ return false; |
+ var result = this._adapter._setSASSText(url, text); |
+ this._handleContentResponse(result); |
+ return !!result; |
+ }, |
+ |
+ /** |
+ * @param {?WebInspector.SASSWorkspaceAdapter.ContentResponse} contentResponse |
+ * @return {?string} |
+ */ |
+ _handleContentResponse: function(contentResponse) |
+ { |
+ if (!contentResponse) |
+ return null; |
+ this._versions.set(contentResponse.url, contentResponse.version); |
+ return contentResponse.text; |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ * @return {!Promise<string>} |
+ */ |
+ content: function(url) |
+ { |
+ return this._adapter._getContent(url) |
+ .then(this._handleContentResponse.bind(this)) |
+ .then(text => text || ""); |
+ }, |
+ |
+ dispose: function() |
+ { |
+ if (this._terminated) |
+ return; |
+ this._terminated = true; |
+ this._adapter._stopTrackSources(this); |
+ this.dispatchEventToListeners(WebInspector.SourceMapTracker.Events.TrackingStopped); |
+ }, |
+ |
+ /** |
+ * @param {string} url |
+ * @param {number} newVersion |
+ */ |
+ _newContentAvailable: function(url, newVersion) |
+ { |
+ if (this._versions.get(url) < newVersion) |
+ this.dispatchEventToListeners(WebInspector.SourceMapTracker.Events.SourceChanged, url); |
+ }, |
+ |
+ __proto__: WebInspector.Object.prototype |
+} |