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.SourceMapping}
34 * @param {!WebInspector.CSSStyleModel} cssModel
35 * @param {!WebInspector.Workspace} workspace
37 WebInspector.StylesSourceMapping = function(cssModel, workspace)
39 this._cssModel = cssModel;
40 this._workspace = workspace;
41 this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectWillReset, this._projectWillReset, this);
42 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this);
43 this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
45 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, this._mainFrameCreatedOrNavigated, this);
47 this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetChanged, this);
51 WebInspector.StylesSourceMapping.MinorChangeUpdateTimeoutMs = 1000;
53 WebInspector.StylesSourceMapping.prototype = {
55 * @param {!WebInspector.RawLocation} rawLocation
56 * @return {?WebInspector.UILocation}
58 rawLocationToUILocation: function(rawLocation)
60 var location = /** @type WebInspector.CSSLocation */ (rawLocation);
61 var uiSourceCode = this._workspace.uiSourceCodeForURL(location.url);
64 return new WebInspector.UILocation(uiSourceCode, location.lineNumber, location.columnNumber);
68 * @param {!WebInspector.UISourceCode} uiSourceCode
69 * @param {number} lineNumber
70 * @param {number} columnNumber
71 * @return {!WebInspector.RawLocation}
73 uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
75 return new WebInspector.CSSLocation(uiSourceCode.url || "", lineNumber, columnNumber);
81 isIdentity: function()
87 * @param {!WebInspector.CSSStyleSheetHeader} header
89 addHeader: function(header)
91 var url = header.resourceURL();
95 header.pushSourceMapping(this);
96 var map = this._urlToHeadersByFrameId[url];
98 map = /** @type {!StringMap.<!StringMap.<!WebInspector.CSSStyleSheetHeader>>} */ (new StringMap());
99 this._urlToHeadersByFrameId[url] = map;
101 var headersById = map.get(header.frameId);
103 headersById = /** @type {!StringMap.<!WebInspector.CSSStyleSheetHeader>} */ (new StringMap());
104 map.put(header.frameId, headersById);
106 headersById.put(header.id, header);
107 var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
109 this._bindUISourceCode(uiSourceCode, header);
113 * @param {!WebInspector.CSSStyleSheetHeader} header
115 removeHeader: function(header)
117 var url = header.resourceURL();
121 var map = this._urlToHeadersByFrameId[url];
123 var headersById = map.get(header.frameId);
124 console.assert(headersById);
125 headersById.remove(header.id);
127 if (!headersById.size()) {
128 map.remove(header.frameId);
130 delete this._urlToHeadersByFrameId[url];
131 var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
133 this._unbindUISourceCode(uiSourceCode);
139 * @param {!WebInspector.UISourceCode} uiSourceCode
141 _unbindUISourceCode: function(uiSourceCode)
143 var styleFile = this._styleFiles.get(uiSourceCode);
147 this._styleFiles.remove(uiSourceCode);
151 * @param {!WebInspector.Event} event
153 _uiSourceCodeAddedToWorkspace: function(event)
155 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
156 var url = uiSourceCode.url;
157 if (!url || !this._urlToHeadersByFrameId[url])
159 this._bindUISourceCode(uiSourceCode, this._urlToHeadersByFrameId[url].values()[0].values()[0]);
163 * @param {!WebInspector.UISourceCode} uiSourceCode
164 * @param {!WebInspector.CSSStyleSheetHeader} header
166 _bindUISourceCode: function(uiSourceCode, header)
168 if (this._styleFiles.get(uiSourceCode) || header.isInline)
170 var url = uiSourceCode.url;
171 this._styleFiles.put(uiSourceCode, new WebInspector.StyleFile(uiSourceCode, this));
172 header.updateLocations();
176 * @param {!WebInspector.Event} event
178 _projectWillReset: function(event)
180 var project = /** @type {!WebInspector.Project} */ (event.data);
181 var uiSourceCodes = project.uiSourceCodes();
182 for (var i = 0; i < uiSourceCodes.length; ++i)
183 this._unbindUISourceCode(uiSourceCodes[i]);
187 * @param {!WebInspector.Event} event
189 _uiSourceCodeRemoved: function(event)
191 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
192 this._unbindUISourceCode(uiSourceCode);
195 _initialize: function()
197 /** @type {!Object.<string, !StringMap.<!StringMap.<!WebInspector.CSSStyleSheetHeader>>>} */
198 this._urlToHeadersByFrameId = {};
199 /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.StyleFile>} */
200 this._styleFiles = new Map();
204 * @param {!WebInspector.Event} event
206 _mainFrameCreatedOrNavigated: function(event)
208 for (var url in this._urlToHeadersByFrameId) {
209 var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
212 this._unbindUISourceCode(uiSourceCode);
218 * @param {!WebInspector.UISourceCode} uiSourceCode
219 * @param {string} content
220 * @param {boolean} majorChange
221 * @param {function(?string)} userCallback
223 _setStyleContent: function(uiSourceCode, content, majorChange, userCallback)
225 var styleSheetIds = this._cssModel.styleSheetIdsForURL(uiSourceCode.url);
226 if (!styleSheetIds.length) {
227 userCallback("No stylesheet found: " + uiSourceCode.url);
231 this._isSettingContent = true;
234 * @param {?Protocol.Error} error
235 * @this {WebInspector.StylesSourceMapping}
237 function callback(error)
240 delete this._isSettingContent;
242 this._cssModel.setStyleSheetText(styleSheetIds[0], content, majorChange, callback.bind(this));
246 * @param {!WebInspector.Event} event
248 _styleSheetChanged: function(event)
250 if (this._isSettingContent)
253 if (event.data.majorChange) {
254 this._updateStyleSheetText(event.data.styleSheetId);
258 this._updateStyleSheetTextSoon(event.data.styleSheetId);
262 * @param {!CSSAgent.StyleSheetId} styleSheetId
264 _updateStyleSheetTextSoon: function(styleSheetId)
266 if (this._updateStyleSheetTextTimer)
267 clearTimeout(this._updateStyleSheetTextTimer);
269 this._updateStyleSheetTextTimer = setTimeout(this._updateStyleSheetText.bind(this, styleSheetId), WebInspector.StylesSourceMapping.MinorChangeUpdateTimeoutMs);
273 * @param {!CSSAgent.StyleSheetId} styleSheetId
275 _updateStyleSheetText: function(styleSheetId)
277 if (this._updateStyleSheetTextTimer) {
278 clearTimeout(this._updateStyleSheetTextTimer);
279 delete this._updateStyleSheetTextTimer;
282 var header = this._cssModel.styleSheetHeaderForId(styleSheetId);
285 var styleSheetURL = header.resourceURL();
288 var uiSourceCode = this._workspace.uiSourceCodeForURL(styleSheetURL)
291 header.requestContent(callback.bind(this, uiSourceCode));
294 * @param {!WebInspector.UISourceCode} uiSourceCode
295 * @param {?string} content
296 * @this {WebInspector.StylesSourceMapping}
298 function callback(uiSourceCode, content)
300 var styleFile = this._styleFiles.get(uiSourceCode);
302 styleFile.addRevision(content || "");
309 * @param {!WebInspector.UISourceCode} uiSourceCode
310 * @param {!WebInspector.StylesSourceMapping} mapping
312 WebInspector.StyleFile = function(uiSourceCode, mapping)
314 this._uiSourceCode = uiSourceCode;
315 this._mapping = mapping;
316 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
317 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
320 WebInspector.StyleFile.updateTimeout = 200;
322 WebInspector.StyleFile.prototype = {
323 _workingCopyCommitted: function(event)
325 if (this._isAddingRevision)
328 this._commitIncrementalEdit(true);
331 _workingCopyChanged: function(event)
333 if (this._isAddingRevision)
336 // FIXME: Extensions tests override updateTimeout because extensions don't have any control over applying changes to domain specific bindings.
337 if (WebInspector.StyleFile.updateTimeout >= 0) {
338 this._incrementalUpdateTimer = setTimeout(this._commitIncrementalEdit.bind(this, false), WebInspector.StyleFile.updateTimeout)
340 this._commitIncrementalEdit(false);
344 * @param {boolean} majorChange
346 _commitIncrementalEdit: function(majorChange)
348 this._clearIncrementalUpdateTimer();
349 this._mapping._setStyleContent(this._uiSourceCode, this._uiSourceCode.workingCopy(), majorChange, this._styleContentSet.bind(this));
353 * @param {?string} error
355 _styleContentSet: function(error)
358 WebInspector.console.showErrorMessage(error);
361 _clearIncrementalUpdateTimer: function()
363 if (!this._incrementalUpdateTimer)
365 clearTimeout(this._incrementalUpdateTimer);
366 delete this._incrementalUpdateTimer;
370 * @param {string} content
372 addRevision: function(content)
374 this._isAddingRevision = true;
375 this._uiSourceCode.addRevision(content);
376 delete this._isAddingRevision;
381 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
382 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);