Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(587)

Unified Diff: third_party/WebKit/Source/devtools/front_end/sass/SASSWorkspaceAdapter.js

Issue 1641893002: DevTools: [SASS] introduce workspace/cssModel adapter for SASS processor. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: remove settimeout from test Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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
+}

Powered by Google App Engine
This is Rietveld 408576698