2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 * @implements {WebInspector.CSSSourceMapping}
34 * @param {!WebInspector.CSSStyleModel} cssModel
35 * @param {!WebInspector.Workspace} workspace
36 * @param {!WebInspector.NetworkMapping} networkMapping
38 WebInspector.StylesSourceMapping = function(cssModel, workspace, networkMapping)
40 this._cssModel = cssModel;
41 this._workspace = workspace;
42 this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved, this);
43 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this);
44 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
45 this._networkMapping = networkMapping;
47 cssModel.target().resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameNavigated, this);
49 this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetChanged, this);
53 WebInspector.StylesSourceMapping.ChangeUpdateTimeoutMs = 200;
55 WebInspector.StylesSourceMapping.prototype = {
58 * @param {!WebInspector.CSSLocation} rawLocation
59 * @return {?WebInspector.UILocation}
61 rawLocationToUILocation: function(rawLocation)
63 var uiSourceCode = this._networkMapping.uiSourceCodeForURL(rawLocation.url, rawLocation.target());
66 var lineNumber = rawLocation.lineNumber;
67 var columnNumber = rawLocation.columnNumber;
68 var header = this._cssModel.styleSheetHeaderForId(rawLocation.styleSheetId);
69 if (header && header.isInline && header.hasSourceURL) {
70 lineNumber -= header.lineNumberInSource(0);
71 columnNumber -= header.columnNumberInSource(lineNumber, 0);
73 return uiSourceCode.uiLocation(lineNumber, columnNumber);
78 * @param {!WebInspector.UISourceCode} uiSourceCode
79 * @param {number} lineNumber
80 * @param {number} columnNumber
81 * @return {?WebInspector.CSSLocation}
83 uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
92 isIdentity: function()
99 * @param {!WebInspector.UISourceCode} uiSourceCode
100 * @param {number} lineNumber
103 uiLineHasMapping: function(uiSourceCode, lineNumber)
109 * @return {!WebInspector.Target}
113 return this._cssModel.target();
117 * @param {!WebInspector.CSSStyleSheetHeader} header
119 addHeader: function(header)
121 var url = header.resourceURL();
125 WebInspector.cssWorkspaceBinding.pushSourceMapping(header, this);
126 var map = this._urlToHeadersByFrameId[url];
128 map = /** @type {!Map.<string, !Map.<string, !WebInspector.CSSStyleSheetHeader>>} */ (new Map());
129 this._urlToHeadersByFrameId[url] = map;
131 var headersById = map.get(header.frameId);
133 headersById = /** @type {!Map.<string, !WebInspector.CSSStyleSheetHeader>} */ (new Map());
134 map.set(header.frameId, headersById);
136 headersById.set(header.id, header);
137 var uiSourceCode = this._networkMapping.uiSourceCodeForURL(url, header.target());
139 this._bindUISourceCode(uiSourceCode, header);
143 * @param {!WebInspector.CSSStyleSheetHeader} header
145 removeHeader: function(header)
147 var url = header.resourceURL();
151 var map = this._urlToHeadersByFrameId[url];
153 var headersById = map.get(header.frameId);
154 console.assert(headersById);
155 headersById.remove(header.id);
157 if (!headersById.size) {
158 map.remove(header.frameId);
160 delete this._urlToHeadersByFrameId[url];
161 var uiSourceCode = this._networkMapping.uiSourceCodeForURL(url, header.target());
163 this._unbindUISourceCode(uiSourceCode);
169 * @param {!WebInspector.UISourceCode} uiSourceCode
171 _unbindUISourceCode: function(uiSourceCode)
173 var styleFile = this._styleFiles.get(uiSourceCode);
177 this._styleFiles.remove(uiSourceCode);
181 * @param {!WebInspector.Event} event
183 _uiSourceCodeAddedToWorkspace: function(event)
185 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
186 var networkURL = this._networkMapping.networkURL(uiSourceCode);
187 if (!networkURL || !this._urlToHeadersByFrameId[networkURL])
189 this._bindUISourceCode(uiSourceCode, this._urlToHeadersByFrameId[networkURL].valuesArray()[0].valuesArray()[0]);
193 * @param {!WebInspector.UISourceCode} uiSourceCode
194 * @param {!WebInspector.CSSStyleSheetHeader} header
196 _bindUISourceCode: function(uiSourceCode, header)
198 if (this._styleFiles.get(uiSourceCode) || (header.isInline && !header.hasSourceURL))
200 this._styleFiles.set(uiSourceCode, new WebInspector.StyleFile(uiSourceCode, this));
201 WebInspector.cssWorkspaceBinding.updateLocations(header);
205 * @param {!WebInspector.Event} event
207 _projectRemoved: function(event)
209 var project = /** @type {!WebInspector.Project} */ (event.data);
210 var uiSourceCodes = project.uiSourceCodes();
211 for (var i = 0; i < uiSourceCodes.length; ++i)
212 this._unbindUISourceCode(uiSourceCodes[i]);
216 * @param {!WebInspector.Event} event
218 _uiSourceCodeRemoved: function(event)
220 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
221 this._unbindUISourceCode(uiSourceCode);
224 _initialize: function()
226 /** @type {!Object.<string, !Map.<string, !Map.<string, !WebInspector.CSSStyleSheetHeader>>>} */
227 this._urlToHeadersByFrameId = {};
228 /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.StyleFile>} */
229 this._styleFiles = new Map();
233 * @param {!WebInspector.Event} event
235 _mainFrameNavigated: function(event)
237 for (var url in this._urlToHeadersByFrameId) {
238 var uiSourceCode = this._networkMapping.uiSourceCodeForURL(url, this._cssModel.target());
241 this._unbindUISourceCode(uiSourceCode);
247 * @param {!WebInspector.UISourceCode} uiSourceCode
248 * @param {string} content
249 * @param {boolean} majorChange
250 * @return {!Promise<?string>}
252 _setStyleContent: function(uiSourceCode, content, majorChange)
254 var networkURL = this._networkMapping.networkURL(uiSourceCode);
255 var styleSheetIds = this._cssModel.styleSheetIdsForURL(networkURL);
256 if (!styleSheetIds.length)
257 return Promise.resolve(/** @type {?string} */("No stylesheet found: " + networkURL));
259 this._isSettingContent = true;
262 * @param {?string} error
263 * @this {WebInspector.StylesSourceMapping}
266 function callback(error)
268 delete this._isSettingContent;
269 return error || null;
273 for (var i = 0; i < styleSheetIds.length; ++i)
274 promises.push(this._cssModel.setStyleSheetText(styleSheetIds[i], content, majorChange));
276 return Promise.all(promises).spread(callback.bind(this));
280 * @param {!WebInspector.Event} event
282 _styleSheetChanged: function(event)
284 if (this._isSettingContent)
287 this._updateStyleSheetTextSoon(event.data.styleSheetId);
291 * @param {!CSSAgent.StyleSheetId} styleSheetId
293 _updateStyleSheetTextSoon: function(styleSheetId)
295 if (this._updateStyleSheetTextTimer)
296 clearTimeout(this._updateStyleSheetTextTimer);
298 this._updateStyleSheetTextTimer = setTimeout(this._updateStyleSheetText.bind(this, styleSheetId), WebInspector.StylesSourceMapping.ChangeUpdateTimeoutMs);
302 * @param {!CSSAgent.StyleSheetId} styleSheetId
304 _updateStyleSheetText: function(styleSheetId)
306 if (this._updateStyleSheetTextTimer) {
307 clearTimeout(this._updateStyleSheetTextTimer);
308 delete this._updateStyleSheetTextTimer;
311 var header = this._cssModel.styleSheetHeaderForId(styleSheetId);
314 var styleSheetURL = header.resourceURL();
317 var uiSourceCode = this._networkMapping.uiSourceCodeForURL(styleSheetURL, header.target());
320 header.requestContent(callback.bind(this, uiSourceCode));
323 * @param {!WebInspector.UISourceCode} uiSourceCode
324 * @param {?string} content
325 * @this {WebInspector.StylesSourceMapping}
327 function callback(uiSourceCode, content)
329 var styleFile = this._styleFiles.get(uiSourceCode);
331 styleFile.addRevision(content || "");
338 * @param {!WebInspector.UISourceCode} uiSourceCode
339 * @param {!WebInspector.StylesSourceMapping} mapping
341 WebInspector.StyleFile = function(uiSourceCode, mapping)
343 this._uiSourceCode = uiSourceCode;
344 this._mapping = mapping;
345 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
346 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
347 this._commitThrottler = new WebInspector.Throttler(WebInspector.StyleFile.updateTimeout);
350 WebInspector.StyleFile.updateTimeout = 200;
352 WebInspector.StyleFile.prototype = {
354 * @param {!WebInspector.Event} event
356 _workingCopyCommitted: function(event)
358 if (this._isAddingRevision)
361 this._isMajorChangePending = true;
362 this._commitThrottler.schedule(this._commitIncrementalEdit.bind(this), true);
366 * @param {!WebInspector.Event} event
368 _workingCopyChanged: function(event)
370 if (this._isAddingRevision)
373 this._commitThrottler.schedule(this._commitIncrementalEdit.bind(this), false);
376 _commitIncrementalEdit: function()
378 var promise = this._mapping._setStyleContent(this._uiSourceCode, this._uiSourceCode.workingCopy(), this._isMajorChangePending)
379 .then(this._styleContentSet.bind(this))
380 this._isMajorChangePending = false;
385 * @param {?string} error
387 _styleContentSet: function(error)
390 WebInspector.console.error(error);
394 * @param {string} content
396 addRevision: function(content)
398 this._isAddingRevision = true;
399 this._uiSourceCode.addRevision(content);
400 delete this._isAddingRevision;
405 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
406 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);