Chromium Code Reviews| Index: third_party/WebKit/Source/devtools/front_end/bindings/StylesSourceMapping.js |
| diff --git a/third_party/WebKit/Source/devtools/front_end/bindings/StylesSourceMapping.js b/third_party/WebKit/Source/devtools/front_end/bindings/StylesSourceMapping.js |
| index 49cb560acdc6cfcf0a820d99581a6e6968d0192a..4c03485271865bc9411f0bda48218166eaaf4d32 100644 |
| --- a/third_party/WebKit/Source/devtools/front_end/bindings/StylesSourceMapping.js |
| +++ b/third_party/WebKit/Source/devtools/front_end/bindings/StylesSourceMapping.js |
| @@ -40,36 +40,104 @@ Bindings.StylesSourceMapping = class { |
| this._cssModel = cssModel; |
| this._workspace = workspace; |
| - /** @type {!Map<string, !Map<string, !Map<string, !SDK.CSSStyleSheetHeader>>>} */ |
| - this._urlToHeadersByFrameId = new Map(); |
| + /** @type {!Map<string, !Bindings.ContentProviderBasedProject>} */ |
| + this._projects = new Map(); |
| + |
| /** @type {!Map.<!Workspace.UISourceCode, !Bindings.StyleFile>} */ |
| this._styleFiles = new Map(); |
| this._eventListeners = [ |
| - this._workspace.addEventListener(Workspace.Workspace.Events.ProjectRemoved, this._projectRemoved, this), |
| - this._workspace.addEventListener( |
| - Workspace.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this), |
| - this._workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this), |
| this._cssModel.addEventListener(SDK.CSSModel.Events.StyleSheetAdded, this._styleSheetAdded, this), |
| this._cssModel.addEventListener(SDK.CSSModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this), |
| - this._cssModel.addEventListener(SDK.CSSModel.Events.StyleSheetChanged, this._styleSheetChanged, this), |
| - SDK.ResourceTreeModel.fromTarget(cssModel.target()) |
| - .addEventListener(SDK.ResourceTreeModel.Events.MainFrameNavigated, this._unbindAllUISourceCodes, this) |
| + this._cssModel.addEventListener(SDK.CSSModel.Events.StyleSheetChanged, this._styleSheetChanged, this) |
| ]; |
| } |
| /** |
| + * @param {!SDK.Target} target |
| + * @param {?SDK.ResourceTreeFrame} frame |
| + * @return {string} |
| + */ |
| + static _projectId(target, frame) { |
| + return 'styles:' + target.id() + ':' + (frame ? frame.id : ''); |
| + } |
| + |
| + /** |
| + * @param {!SDK.CSSStyleSheetHeader} header |
| + * @return {!Bindings.ContentProviderBasedProject} |
| + */ |
| + _createProject(header) { |
| + var target = header.cssModel().target(); |
| + var frame = SDK.ResourceTreeFrame.fromStyleSheet(header); |
| + var projectId = Bindings.StylesSourceMapping._projectId(target, frame); |
| + var project = new Bindings.ContentProviderBasedProject( |
| + this._workspace, projectId, Workspace.projectTypes.Network, '', false /* isServiceProject */); |
| + Bindings.NetworkProject.annotateProjectWithTargetAndFrame(project, target, frame); |
| + this._projects.set(projectId, project); |
| + return project; |
| + } |
| + |
| + /** |
| + * @param {!SDK.CSSStyleSheetHeader} header |
| + * @return {?Bindings.ContentProviderBasedProject} |
| + */ |
| + _getProject(header) { |
| + var target = header.cssModel().target(); |
| + var frame = SDK.ResourceTreeFrame.fromStyleSheet(header); |
| + return this._projects.get(Bindings.StylesSourceMapping._projectId(target, frame)) || null; |
| + } |
| + |
| + /** |
| + * @param {!SDK.CSSStyleSheetHeader} header |
| + * @return {!Workspace.UISourceCode} |
| + */ |
| + _createUISourceCode(header) { |
| + var project = this._getProject(header); |
| + if (!project) |
| + project = this._createProject(header); |
| + |
| + var originalContentProvider = header.originalContentProvider(); |
| + var url = originalContentProvider.contentURL(); |
| + var uiSourceCode = project.createUISourceCode(url, originalContentProvider.contentType()); |
| + Bindings.NetworkProject.useExplicitMimeType(uiSourceCode); |
| + var resource = SDK.ResourceTreeModel.resourceForURL(url); |
|
dgozman
2017/02/15 19:57:11
- header.target().model(SDK.ResourceTreeModel).res
lushnikov
2017/02/23 02:02:35
Done along this lines.
|
| + var metadata = Bindings.NetworkProject.resourceMetadata(resource); |
| + project.addUISourceCodeWithProvider(uiSourceCode, originalContentProvider, metadata); |
| + return uiSourceCode; |
| + } |
| + |
| + /** |
| + * @param {!SDK.CSSStyleSheetHeader} header |
| + * @return {?Workspace.UISourceCode} |
| + */ |
| + _getUISourceCode(header) { |
| + var project = this._getProject(header); |
| + return project ? project.uiSourceCodeForURL(header.contentURL()) : null; |
| + } |
| + |
| + /** |
| + * @param {!Workspace.UISourceCode} uiSourceCode |
| + */ |
| + _removeUISourceCode(uiSourceCode) { |
| + var project = /** @type {!Bindings.ContentProviderBasedProject} */ (uiSourceCode.project()); |
| + project.removeFile(uiSourceCode.url()); |
| + if (project.isEmpty()) { |
| + project.removeProject(); |
| + this._projects.delete(project.id()); |
| + } |
| + } |
| + |
| + /** |
| * @param {!SDK.CSSLocation} rawLocation |
| * @return {?Workspace.UILocation} |
| */ |
| rawLocationToUILocation(rawLocation) { |
| - var uiSourceCode = |
| - Bindings.NetworkProject.uiSourceCodeForStyleURL(this._workspace, rawLocation.url, rawLocation.header()); |
| + var uiSourceCode = this._getUISourceCode(rawLocation.header()); |
| if (!uiSourceCode) |
| return null; |
| var lineNumber = rawLocation.lineNumber; |
| var columnNumber = rawLocation.columnNumber; |
| - var header = this._cssModel.styleSheetHeaderForId(rawLocation.styleSheetId); |
| + var header = rawLocation.header(); |
| if (header && header.isInline && header.hasSourceURL) { |
| lineNumber -= header.lineNumberInSource(0); |
| columnNumber -= header.columnNumberInSource(lineNumber, 0); |
| @@ -82,24 +150,22 @@ Bindings.StylesSourceMapping = class { |
| */ |
| _styleSheetAdded(event) { |
| var header = /** @type {!SDK.CSSStyleSheetHeader} */ (event.data); |
| + if (header.isInline && !header.hasSourceURL && header.origin !== 'inspector') |
| + return; |
| + |
| var url = header.resourceURL(); |
| if (!url) |
| return; |
| - var map = this._urlToHeadersByFrameId.get(url); |
| - if (!map) { |
| - map = /** @type {!Map.<string, !Map.<string, !SDK.CSSStyleSheetHeader>>} */ (new Map()); |
| - this._urlToHeadersByFrameId.set(url, map); |
| - } |
| - var headersById = map.get(header.frameId); |
| - if (!headersById) { |
| - headersById = /** @type {!Map.<string, !SDK.CSSStyleSheetHeader>} */ (new Map()); |
| - map.set(header.frameId, headersById); |
| + var uiSourceCode = this._getUISourceCode(header); |
| + if (!uiSourceCode) { |
| + uiSourceCode = this._createUISourceCode(header); |
| + this._styleFiles.set(uiSourceCode, new Bindings.StyleFile(this._cssModel, uiSourceCode)); |
| } |
| - headersById.set(header.id, header); |
| - var uiSourceCode = Bindings.NetworkProject.uiSourceCodeForStyleURL(this._workspace, url, header); |
| - if (uiSourceCode) |
| - this._bindUISourceCode(uiSourceCode, header); |
| + var styleFile = this._styleFiles.get(uiSourceCode); |
| + styleFile.addHeader(header); |
| + |
| + Bindings.cssWorkspaceBinding.updateLocations(header); |
| } |
| /** |
| @@ -107,247 +173,185 @@ Bindings.StylesSourceMapping = class { |
| */ |
| _styleSheetRemoved(event) { |
| var header = /** @type {!SDK.CSSStyleSheetHeader} */ (event.data); |
| - var url = header.resourceURL(); |
| - if (!url) |
| + var uiSourceCode = this._getUISourceCode(header); |
| + if (!uiSourceCode) |
| return; |
| - |
| - var map = this._urlToHeadersByFrameId.get(url); |
| - console.assert(map); |
| - var headersById = map.get(header.frameId); |
| - console.assert(headersById); |
| - headersById.delete(header.id); |
| - |
| - if (!headersById.size) { |
| - map.delete(header.frameId); |
| - if (!map.size) { |
| - this._urlToHeadersByFrameId.delete(url); |
| - var uiSourceCode = Bindings.NetworkProject.uiSourceCodeForStyleURL(this._workspace, url, header); |
| - if (uiSourceCode) |
| - this._unbindUISourceCode(uiSourceCode); |
| - } |
| - } |
| - } |
| - |
| - /** |
| - * @param {!Workspace.UISourceCode} uiSourceCode |
| - */ |
| - _unbindUISourceCode(uiSourceCode) { |
| var styleFile = this._styleFiles.get(uiSourceCode); |
| - if (!styleFile) |
| + styleFile.removeHeader(header); |
| + if (styleFile.hasHeaders()) |
| return; |
| styleFile.dispose(); |
| this._styleFiles.delete(uiSourceCode); |
| + this._removeUISourceCode(uiSourceCode); |
| } |
| /** |
| * @param {!Common.Event} event |
| */ |
| - _unbindAllUISourceCodes(event) { |
| - if (event.data.target() !== this._cssModel.target()) |
| + _styleSheetChanged(event) { |
| + var styleSheetId = event.data.styleSheetId; |
|
dgozman
2017/02/15 19:57:11
@type ?
lushnikov
2017/02/23 02:02:35
Done.
|
| + var header = this._cssModel.styleSheetHeaderForId(styleSheetId); |
|
dgozman
2017/02/15 19:57:11
Let's pass a header in an event instead?
lushnikov
2017/02/23 02:02:35
I'll postpone this change - this CL already has a
|
| + if (!header) |
| return; |
| - for (var styleFile of this._styleFiles.values()) |
| + var uiSourceCode = this._getUISourceCode(header); |
| + var styleFile = uiSourceCode ? this._styleFiles.get(uiSourceCode) : null; |
|
dgozman
2017/02/15 19:57:11
if (!uiSourceCode) return;
lushnikov
2017/02/23 02:02:35
Done.
|
| + if (styleFile) |
| + styleFile.styleSheetChanged(header); |
| + } |
| + |
| + dispose() { |
| + for (var uiSourceCode of this._styleFiles.keys()) { |
| + this._removeUISourceCode(uiSourceCode); |
| + var styleFile = this._styleFiles.get(uiSourceCode); |
| styleFile.dispose(); |
| + } |
| this._styleFiles.clear(); |
| - this._urlToHeadersByFrameId = new Map(); |
| + Common.EventTarget.removeEventListeners(this._eventListeners); |
| } |
| +}; |
| +/** |
| + * @unrestricted |
| + */ |
| +Bindings.StyleFile = class { |
| /** |
| - * @param {!Common.Event} event |
| + * @param {!SDK.CSSModel} cssModel |
| + * @param {!Workspace.UISourceCode} uiSourceCode |
| */ |
| - _uiSourceCodeAddedToWorkspace(event) { |
| - var uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data); |
| - if (!this._urlToHeadersByFrameId.has(uiSourceCode.url())) |
| - return; |
| - this._bindUISourceCode( |
| - uiSourceCode, this._urlToHeadersByFrameId.get(uiSourceCode.url()).valuesArray()[0].valuesArray()[0]); |
| + constructor(cssModel, uiSourceCode) { |
| + this._cssModel = cssModel; |
| + this._uiSourceCode = uiSourceCode; |
| + /** @type {!Set<!SDK.CSSStyleSheetHeader>} */ |
| + this._headers = new Set(); |
| + this._eventListeners = [ |
| + this._uiSourceCode.addEventListener( |
| + Workspace.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this), |
| + this._uiSourceCode.addEventListener( |
| + Workspace.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this) |
| + ]; |
| + this._commitThrottler = new Common.Throttler(Bindings.StyleFile.updateTimeout); |
| + this._terminated = false; |
| } |
| /** |
| - * @param {!Workspace.UISourceCode} uiSourceCode |
| * @param {!SDK.CSSStyleSheetHeader} header |
| */ |
| - _bindUISourceCode(uiSourceCode, header) { |
| - if (this._styleFiles.get(uiSourceCode) || (header.isInline && !header.hasSourceURL)) |
| - return; |
| - this._styleFiles.set(uiSourceCode, new Bindings.StyleFile(uiSourceCode, this)); |
| - Bindings.cssWorkspaceBinding.updateLocations(header); |
| + addHeader(header) { |
| + this._headers.add(header); |
| } |
| /** |
| - * @param {!Common.Event} event |
| + * @param {!SDK.CSSStyleSheetHeader} header |
| */ |
| - _projectRemoved(event) { |
| - var project = /** @type {!Workspace.Project} */ (event.data); |
| - var uiSourceCodes = project.uiSourceCodes(); |
| - for (var i = 0; i < uiSourceCodes.length; ++i) |
| - this._unbindUISourceCode(uiSourceCodes[i]); |
| + removeHeader(header) { |
| + this._headers.delete(header); |
| } |
| /** |
| - * @param {!Common.Event} event |
| + * @return {boolean} |
| */ |
| - _uiSourceCodeRemoved(event) { |
| - var uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data); |
| - this._unbindUISourceCode(uiSourceCode); |
| + hasHeaders() { |
| + return !!this._headers.size; |
| } |
| /** |
| - * @param {!Workspace.UISourceCode} uiSourceCode |
| - * @param {string} content |
| - * @param {boolean} majorChange |
| - * @return {!Promise<?string>} |
| + * @param {!SDK.CSSStyleSheetHeader} header |
| */ |
| - _setStyleContent(uiSourceCode, content, majorChange) { |
| - var styleSheetIds = this._cssModel.styleSheetIdsForURL(uiSourceCode.url()); |
| - if (!styleSheetIds.length) |
| - return Promise.resolve(/** @type {?string} */ ('No stylesheet found: ' + uiSourceCode.url())); |
| - |
| - this._isSettingContent = true; |
| - |
| - /** |
| - * @param {?string} error |
| - * @this {Bindings.StylesSourceMapping} |
| - * @return {?string} |
| - */ |
| - function callback(error) { |
| - delete this._isSettingContent; |
| - return error || null; |
| - } |
| - |
| - var promises = []; |
| - for (var i = 0; i < styleSheetIds.length; ++i) |
| - promises.push(this._cssModel.setStyleSheetText(styleSheetIds[i], content, majorChange)); |
| + styleSheetChanged(header) { |
| + if (this._isSettingStyleSheetContents) |
| + return; |
| - return Promise.all(promises).spread(callback.bind(this)); |
| + this._commitThrottler.schedule(this._syncStyleSheetChange.bind(this, header), false); |
| } |
| /** |
| * @param {!Common.Event} event |
| */ |
| - _styleSheetChanged(event) { |
| - if (this._isSettingContent) |
| + _workingCopyCommitted(event) { |
| + if (this._isAddingRevision) |
| return; |
| - this._updateStyleSheetTextSoon(event.data.styleSheetId); |
| + this._isMajorChangePending = true; |
| + this._commitThrottler.schedule(this._syncUISourceCodeChange.bind(this), true); |
| } |
| /** |
| - * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| + * @param {!Common.Event} event |
| */ |
| - _updateStyleSheetTextSoon(styleSheetId) { |
| - if (this._updateStyleSheetTextTimer) |
| - clearTimeout(this._updateStyleSheetTextTimer); |
| + _workingCopyChanged(event) { |
| + if (this._isAddingRevision) |
| + return; |
| - this._updateStyleSheetTextTimer = setTimeout( |
| - this._updateStyleSheetText.bind(this, styleSheetId), Bindings.StylesSourceMapping.ChangeUpdateTimeoutMs); |
| + this._commitThrottler.schedule(this._syncUISourceCodeChange.bind(this), false); |
| } |
| /** |
| - * @param {!Protocol.CSS.StyleSheetId} styleSheetId |
| + * @param {!SDK.CSSStyleSheetHeader} header |
| + * @return {!Promise} |
| */ |
| - _updateStyleSheetText(styleSheetId) { |
| - if (this._updateStyleSheetTextTimer) { |
| - clearTimeout(this._updateStyleSheetTextTimer); |
| - delete this._updateStyleSheetTextTimer; |
| - } |
| + _syncStyleSheetChange(header) { |
| + if (this._terminated || !this._headers.has(header)) |
| + return Promise.resolve(); |
| - var header = this._cssModel.styleSheetHeaderForId(styleSheetId); |
| - if (!header) |
| - return; |
| - var styleSheetURL = header.resourceURL(); |
| - if (!styleSheetURL) |
| - return; |
| - var uiSourceCode = Bindings.NetworkProject.uiSourceCodeForStyleURL(this._workspace, styleSheetURL, header); |
| - if (!uiSourceCode) |
| - return; |
| - header.requestContent().then(callback.bind(this, uiSourceCode)); |
| + return header.requestContent().then(onHeaderContent.bind(this)); |
| /** |
| - * @param {!Workspace.UISourceCode} uiSourceCode |
| * @param {?string} content |
| - * @this {Bindings.StylesSourceMapping} |
| + * @return {!Promise} |
| + * @this {Bindings.StyleFile} |
| */ |
| - function callback(uiSourceCode, content) { |
| - var styleFile = this._styleFiles.get(uiSourceCode); |
| - if (styleFile) |
| - styleFile.addRevision(content || ''); |
| + function onHeaderContent(content) { |
| + if (this._terminated || content === null) |
| + return Promise.resolve(); |
| + this._isAddingRevision = true; |
| + this._uiSourceCode.addRevision(content); |
| + delete this._isAddingRevision; |
| + return this._setStyleSheetContents(content, header, true); |
| } |
| } |
| - dispose() { |
| - Common.EventTarget.removeEventListeners(this._eventListeners); |
| - } |
| -}; |
| - |
| -Bindings.StylesSourceMapping.ChangeUpdateTimeoutMs = 200; |
| - |
| -/** |
| - * @unrestricted |
| - */ |
| -Bindings.StyleFile = class { |
| - /** |
| - * @param {!Workspace.UISourceCode} uiSourceCode |
| - * @param {!Bindings.StylesSourceMapping} mapping |
| - */ |
| - constructor(uiSourceCode, mapping) { |
| - this._uiSourceCode = uiSourceCode; |
| - this._mapping = mapping; |
| - this._eventListeners = [ |
| - this._uiSourceCode.addEventListener( |
| - Workspace.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this), |
| - this._uiSourceCode.addEventListener( |
| - Workspace.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this) |
| - ]; |
| - this._commitThrottler = new Common.Throttler(Bindings.StyleFile.updateTimeout); |
| - this._terminated = false; |
| - } |
| - |
| /** |
| - * @param {!Common.Event} event |
| + * @return {!Promise} |
| */ |
| - _workingCopyCommitted(event) { |
| - if (this._isAddingRevision) |
| - return; |
| - |
| - this._isMajorChangePending = true; |
| - this._commitThrottler.schedule(this._commitIncrementalEdit.bind(this), true); |
| - } |
| - |
| - /** |
| - * @param {!Common.Event} event |
| - */ |
| - _workingCopyChanged(event) { |
| - if (this._isAddingRevision) |
| - return; |
| - |
| - this._commitThrottler.schedule(this._commitIncrementalEdit.bind(this), false); |
| - } |
| - |
| - _commitIncrementalEdit() { |
| + _syncUISourceCodeChange() { |
| if (this._terminated) |
| - return; |
| - var promise = |
| - this._mapping._setStyleContent(this._uiSourceCode, this._uiSourceCode.workingCopy(), this._isMajorChangePending) |
| - .then(this._styleContentSet.bind(this)); |
| + return Promise.resolve(); |
| + var promise = this._setStyleSheetContents(this._uiSourceCode.workingCopy(), null, this._isMajorChangePending); |
| this._isMajorChangePending = false; |
| return promise; |
| } |
| /** |
| - * @param {?string} error |
| + * @param {string} content |
| + * @param {?SDK.CSSStyleSheetHeader} skipStyleSheet |
| + * @param {boolean} majorChange |
| + * @return {!Promise} |
| */ |
| - _styleContentSet(error) { |
| - if (error) |
| - console.error(error); |
| + _setStyleSheetContents(content, skipStyleSheet, majorChange) { |
| + this._isSettingStyleSheetContents = true; |
| + var promises = []; |
| + for (var header of this._headers) { |
| + if (header === skipStyleSheet) |
| + continue; |
| + promises.push(this._cssModel.setStyleSheetText(header.id, content, majorChange)); |
| + } |
| + |
| + return Promise.all(promises).then(onStyleSheetContentsUpdated.bind(this)); |
| + |
| + /** |
| + * @param {!Array<?Protocol.Error>} errors |
| + * @this {Bindings.StyleFile} |
| + */ |
| + function onStyleSheetContentsUpdated(errors) { |
| + delete this._isSettingStyleSheetContents; |
| + errors = errors.filter(error => !!error); |
|
dgozman
2017/02/15 19:57:11
errors.filter(error => !!error).map(console.error)
lushnikov
2017/02/23 02:02:35
Done.
|
| + for (var error of errors) |
| + console.error(error); |
| + this._styleFileSyncedForTest(); |
| + } |
| } |
| - /** |
| - * @param {string} content |
| - */ |
| - addRevision(content) { |
| - this._isAddingRevision = true; |
| - this._uiSourceCode.addRevision(content); |
| - delete this._isAddingRevision; |
| + _styleFileSyncedForTest() { |
| } |
| dispose() { |