Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / StylesSourceMapping.js
1 /*
2  * Copyright (C) 2012 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
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
13  * distribution.
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.
17  *
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.
29  */
30
31 /**
32  * @constructor
33  * @implements {WebInspector.SourceMapping}
34  * @param {!WebInspector.CSSStyleModel} cssModel
35  * @param {!WebInspector.Workspace} workspace
36  */
37 WebInspector.StylesSourceMapping = function(cssModel, workspace)
38 {
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);
44
45     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, this._mainFrameCreatedOrNavigated, this);
46
47     this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetChanged, this);
48     this._initialize();
49 }
50
51 WebInspector.StylesSourceMapping.MinorChangeUpdateTimeoutMs = 1000;
52
53 WebInspector.StylesSourceMapping.prototype = {
54     /**
55      * @param {!WebInspector.RawLocation} rawLocation
56      * @return {?WebInspector.UILocation}
57      */
58     rawLocationToUILocation: function(rawLocation)
59     {
60         var location = /** @type WebInspector.CSSLocation */ (rawLocation);
61         var uiSourceCode = this._workspace.uiSourceCodeForURL(location.url);
62         if (!uiSourceCode)
63             return null;
64         return new WebInspector.UILocation(uiSourceCode, location.lineNumber, location.columnNumber);
65     },
66
67     /**
68      * @param {!WebInspector.UISourceCode} uiSourceCode
69      * @param {number} lineNumber
70      * @param {number} columnNumber
71      * @return {!WebInspector.RawLocation}
72      */
73     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
74     {
75         return new WebInspector.CSSLocation(uiSourceCode.url || "", lineNumber, columnNumber);
76     },
77
78     /**
79      * @return {boolean}
80      */
81     isIdentity: function()
82     {
83         return true;
84     },
85
86     /**
87      * @param {!WebInspector.CSSStyleSheetHeader} header
88      */
89     addHeader: function(header)
90     {
91         var url = header.resourceURL();
92         if (!url)
93             return;
94
95         header.pushSourceMapping(this);
96         var map = this._urlToHeadersByFrameId[url];
97         if (!map) {
98             map = /** @type {!StringMap.<!StringMap.<!WebInspector.CSSStyleSheetHeader>>} */ (new StringMap());
99             this._urlToHeadersByFrameId[url] = map;
100         }
101         var headersById = map.get(header.frameId);
102         if (!headersById) {
103             headersById = /** @type {!StringMap.<!WebInspector.CSSStyleSheetHeader>} */ (new StringMap());
104             map.put(header.frameId, headersById);
105         }
106         headersById.put(header.id, header);
107         var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
108         if (uiSourceCode)
109             this._bindUISourceCode(uiSourceCode, header);
110     },
111
112     /**
113      * @param {!WebInspector.CSSStyleSheetHeader} header
114      */
115     removeHeader: function(header)
116     {
117         var url = header.resourceURL();
118         if (!url)
119             return;
120
121         var map = this._urlToHeadersByFrameId[url];
122         console.assert(map);
123         var headersById = map.get(header.frameId);
124         console.assert(headersById);
125         headersById.remove(header.id);
126
127         if (!headersById.size()) {
128             map.remove(header.frameId);
129             if (!map.size()) {
130                 delete this._urlToHeadersByFrameId[url];
131                 var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
132                 if (uiSourceCode)
133                     this._unbindUISourceCode(uiSourceCode);
134             }
135         }
136     },
137
138     /**
139      * @param {!WebInspector.UISourceCode} uiSourceCode
140      */
141     _unbindUISourceCode: function(uiSourceCode)
142     {
143         var styleFile = this._styleFiles.get(uiSourceCode);
144         if (!styleFile)
145             return;
146         styleFile.dispose();
147         this._styleFiles.remove(uiSourceCode);
148     },
149
150     /**
151      * @param {!WebInspector.Event} event
152      */
153     _uiSourceCodeAddedToWorkspace: function(event)
154     {
155         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
156         var url = uiSourceCode.url;
157         if (!url || !this._urlToHeadersByFrameId[url])
158             return;
159         this._bindUISourceCode(uiSourceCode, this._urlToHeadersByFrameId[url].values()[0].values()[0]);
160     },
161
162     /**
163      * @param {!WebInspector.UISourceCode} uiSourceCode
164      * @param {!WebInspector.CSSStyleSheetHeader} header
165      */
166     _bindUISourceCode: function(uiSourceCode, header)
167     {
168         if (this._styleFiles.get(uiSourceCode) || header.isInline)
169             return;
170         var url = uiSourceCode.url;
171         this._styleFiles.put(uiSourceCode, new WebInspector.StyleFile(uiSourceCode, this));
172         header.updateLocations();
173     },
174
175     /**
176      * @param {!WebInspector.Event} event
177      */
178     _projectWillReset: function(event)
179     {
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]);
184     },
185
186     /**
187      * @param {!WebInspector.Event} event
188      */
189     _uiSourceCodeRemoved: function(event)
190     {
191         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
192         this._unbindUISourceCode(uiSourceCode);
193     },
194
195     _initialize: function()
196     {
197         /** @type {!Object.<string, !StringMap.<!StringMap.<!WebInspector.CSSStyleSheetHeader>>>} */
198         this._urlToHeadersByFrameId = {};
199         /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.StyleFile>} */
200         this._styleFiles = new Map();
201     },
202
203     /**
204      * @param {!WebInspector.Event} event
205      */
206     _mainFrameCreatedOrNavigated: function(event)
207     {
208         for (var url in this._urlToHeadersByFrameId) {
209             var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
210             if (!uiSourceCode)
211                 continue;
212             this._unbindUISourceCode(uiSourceCode);
213         }
214         this._initialize();
215     },
216
217     /**
218      * @param {!WebInspector.UISourceCode} uiSourceCode
219      * @param {string} content
220      * @param {boolean} majorChange
221      * @param {function(?string)} userCallback
222      */
223     _setStyleContent: function(uiSourceCode, content, majorChange, userCallback)
224     {
225         var styleSheetIds = this._cssModel.styleSheetIdsForURL(uiSourceCode.url);
226         if (!styleSheetIds.length) {
227             userCallback("No stylesheet found: " + uiSourceCode.url);
228             return;
229         }
230
231         this._isSettingContent = true;
232
233         /**
234          * @param {?Protocol.Error} error
235          * @this {WebInspector.StylesSourceMapping}
236          */
237         function callback(error)
238         {
239             userCallback(error);
240             delete this._isSettingContent;
241         }
242         this._cssModel.setStyleSheetText(styleSheetIds[0], content, majorChange, callback.bind(this));
243     },
244
245     /**
246      * @param {!WebInspector.Event} event
247      */
248     _styleSheetChanged: function(event)
249     {
250         if (this._isSettingContent)
251             return;
252
253         if (event.data.majorChange) {
254             this._updateStyleSheetText(event.data.styleSheetId);
255             return;
256         }
257
258         this._updateStyleSheetTextSoon(event.data.styleSheetId);
259     },
260
261     /**
262      * @param {!CSSAgent.StyleSheetId} styleSheetId
263      */
264     _updateStyleSheetTextSoon: function(styleSheetId)
265     {
266         if (this._updateStyleSheetTextTimer)
267             clearTimeout(this._updateStyleSheetTextTimer);
268
269         this._updateStyleSheetTextTimer = setTimeout(this._updateStyleSheetText.bind(this, styleSheetId), WebInspector.StylesSourceMapping.MinorChangeUpdateTimeoutMs);
270     },
271
272     /**
273      * @param {!CSSAgent.StyleSheetId} styleSheetId
274      */
275     _updateStyleSheetText: function(styleSheetId)
276     {
277         if (this._updateStyleSheetTextTimer) {
278             clearTimeout(this._updateStyleSheetTextTimer);
279             delete this._updateStyleSheetTextTimer;
280         }
281
282         var header = this._cssModel.styleSheetHeaderForId(styleSheetId);
283         if (!header)
284             return;
285         var styleSheetURL = header.resourceURL();
286         if (!styleSheetURL)
287             return;
288         var uiSourceCode = this._workspace.uiSourceCodeForURL(styleSheetURL)
289         if (!uiSourceCode)
290             return;
291         header.requestContent(callback.bind(this, uiSourceCode));
292
293         /**
294          * @param {!WebInspector.UISourceCode} uiSourceCode
295          * @param {?string} content
296          * @this {WebInspector.StylesSourceMapping}
297          */
298         function callback(uiSourceCode, content)
299         {
300             var styleFile = this._styleFiles.get(uiSourceCode);
301             if (styleFile)
302                 styleFile.addRevision(content || "");
303         }
304     }
305 }
306
307 /**
308  * @constructor
309  * @param {!WebInspector.UISourceCode} uiSourceCode
310  * @param {!WebInspector.StylesSourceMapping} mapping
311  */
312 WebInspector.StyleFile = function(uiSourceCode, mapping)
313 {
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);
318 }
319
320 WebInspector.StyleFile.updateTimeout = 200;
321
322 WebInspector.StyleFile.prototype = {
323     _workingCopyCommitted: function(event)
324     {
325         if (this._isAddingRevision)
326             return;
327
328         this._commitIncrementalEdit(true);
329     },
330
331     _workingCopyChanged: function(event)
332     {
333         if (this._isAddingRevision)
334             return;
335
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)
339         } else
340             this._commitIncrementalEdit(false);
341     },
342
343     /**
344      * @param {boolean} majorChange
345      */
346     _commitIncrementalEdit: function(majorChange)
347     {
348         this._clearIncrementalUpdateTimer();
349         this._mapping._setStyleContent(this._uiSourceCode, this._uiSourceCode.workingCopy(), majorChange, this._styleContentSet.bind(this));
350     },
351
352     /**
353      * @param {?string} error
354      */
355     _styleContentSet: function(error)
356     {
357         if (error)
358             WebInspector.console.showErrorMessage(error);
359     },
360
361     _clearIncrementalUpdateTimer: function()
362     {
363         if (!this._incrementalUpdateTimer)
364             return;
365         clearTimeout(this._incrementalUpdateTimer);
366         delete this._incrementalUpdateTimer;
367     },
368
369     /**
370      * @param {string} content
371      */
372     addRevision: function(content)
373     {
374         this._isAddingRevision = true;
375         this._uiSourceCode.addRevision(content);
376         delete this._isAddingRevision;
377     },
378
379     dispose: function()
380     {
381         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
382         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
383     }
384 }