Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / bindings / 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.CSSSourceMapping}
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.ProjectRemoved, this._projectRemoved, 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.MainFrameNavigated, this._mainFrameNavigated, 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.CSSLocation} 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 uiSourceCode.uiLocation(location.lineNumber, location.columnNumber);
65     },
66
67     /**
68      * @param {!WebInspector.UISourceCode} uiSourceCode
69      * @param {number} lineNumber
70      * @param {number} columnNumber
71      * @return {!WebInspector.CSSLocation}
72      */
73     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
74     {
75         return new WebInspector.CSSLocation(this._cssModel.target(), null, uiSourceCode.url || "", lineNumber, columnNumber);
76     },
77
78     /**
79      * @return {boolean}
80      */
81     isIdentity: function()
82     {
83         return true;
84     },
85
86     /**
87      * @param {!WebInspector.UISourceCode} uiSourceCode
88      * @param {number} lineNumber
89      * @return {boolean}
90      */
91     uiLineHasMapping: function(uiSourceCode, lineNumber)
92     {
93         return true;
94     },
95
96     /**
97      * @return {!WebInspector.Target}
98      */
99     target: function()
100     {
101         return this._cssModel.target();
102     },
103
104     /**
105      * @param {!WebInspector.CSSStyleSheetHeader} header
106      */
107     addHeader: function(header)
108     {
109         var url = header.resourceURL();
110         if (!url)
111             return;
112
113         WebInspector.cssWorkspaceBinding.pushSourceMapping(header, this);
114         var map = this._urlToHeadersByFrameId[url];
115         if (!map) {
116             map = /** @type {!StringMap.<!StringMap.<!WebInspector.CSSStyleSheetHeader>>} */ (new StringMap());
117             this._urlToHeadersByFrameId[url] = map;
118         }
119         var headersById = map.get(header.frameId);
120         if (!headersById) {
121             headersById = /** @type {!StringMap.<!WebInspector.CSSStyleSheetHeader>} */ (new StringMap());
122             map.put(header.frameId, headersById);
123         }
124         headersById.put(header.id, header);
125         var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
126         if (uiSourceCode)
127             this._bindUISourceCode(uiSourceCode, header);
128     },
129
130     /**
131      * @param {!WebInspector.CSSStyleSheetHeader} header
132      */
133     removeHeader: function(header)
134     {
135         var url = header.resourceURL();
136         if (!url)
137             return;
138
139         var map = this._urlToHeadersByFrameId[url];
140         console.assert(map);
141         var headersById = map.get(header.frameId);
142         console.assert(headersById);
143         headersById.remove(header.id);
144
145         if (!headersById.size()) {
146             map.remove(header.frameId);
147             if (!map.size()) {
148                 delete this._urlToHeadersByFrameId[url];
149                 var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
150                 if (uiSourceCode)
151                     this._unbindUISourceCode(uiSourceCode);
152             }
153         }
154     },
155
156     /**
157      * @param {!WebInspector.UISourceCode} uiSourceCode
158      */
159     _unbindUISourceCode: function(uiSourceCode)
160     {
161         var styleFile = this._styleFiles.get(uiSourceCode);
162         if (!styleFile)
163             return;
164         styleFile.dispose();
165         this._styleFiles.remove(uiSourceCode);
166     },
167
168     /**
169      * @param {!WebInspector.Event} event
170      */
171     _uiSourceCodeAddedToWorkspace: function(event)
172     {
173         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
174         var url = uiSourceCode.url;
175         if (!url || !this._urlToHeadersByFrameId[url])
176             return;
177         this._bindUISourceCode(uiSourceCode, this._urlToHeadersByFrameId[url].values()[0].values()[0]);
178     },
179
180     /**
181      * @param {!WebInspector.UISourceCode} uiSourceCode
182      * @param {!WebInspector.CSSStyleSheetHeader} header
183      */
184     _bindUISourceCode: function(uiSourceCode, header)
185     {
186         if (this._styleFiles.get(uiSourceCode) || header.isInline)
187             return;
188         var url = uiSourceCode.url;
189         this._styleFiles.put(uiSourceCode, new WebInspector.StyleFile(uiSourceCode, this));
190         WebInspector.cssWorkspaceBinding.updateLocations(header);
191     },
192
193     /**
194      * @param {!WebInspector.Event} event
195      */
196     _projectRemoved: function(event)
197     {
198         var project = /** @type {!WebInspector.Project} */ (event.data);
199         var uiSourceCodes = project.uiSourceCodes();
200         for (var i = 0; i < uiSourceCodes.length; ++i)
201             this._unbindUISourceCode(uiSourceCodes[i]);
202     },
203
204     /**
205      * @param {!WebInspector.Event} event
206      */
207     _uiSourceCodeRemoved: function(event)
208     {
209         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
210         this._unbindUISourceCode(uiSourceCode);
211     },
212
213     _initialize: function()
214     {
215         /** @type {!Object.<string, !StringMap.<!StringMap.<!WebInspector.CSSStyleSheetHeader>>>} */
216         this._urlToHeadersByFrameId = {};
217         /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.StyleFile>} */
218         this._styleFiles = new Map();
219     },
220
221     /**
222      * @param {!WebInspector.Event} event
223      */
224     _mainFrameNavigated: function(event)
225     {
226         for (var url in this._urlToHeadersByFrameId) {
227             var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
228             if (!uiSourceCode)
229                 continue;
230             this._unbindUISourceCode(uiSourceCode);
231         }
232         this._initialize();
233     },
234
235     /**
236      * @param {!WebInspector.UISourceCode} uiSourceCode
237      * @param {string} content
238      * @param {boolean} majorChange
239      * @param {function(?string)} userCallback
240      */
241     _setStyleContent: function(uiSourceCode, content, majorChange, userCallback)
242     {
243         var styleSheetIds = this._cssModel.styleSheetIdsForURL(uiSourceCode.url);
244         if (!styleSheetIds.length) {
245             userCallback("No stylesheet found: " + uiSourceCode.url);
246             return;
247         }
248
249         this._isSettingContent = true;
250
251         /**
252          * @param {?Protocol.Error} error
253          * @this {WebInspector.StylesSourceMapping}
254          */
255         function callback(error)
256         {
257             userCallback(error);
258             delete this._isSettingContent;
259         }
260         this._cssModel.setStyleSheetText(styleSheetIds[0], content, majorChange, callback.bind(this));
261     },
262
263     /**
264      * @param {!WebInspector.Event} event
265      */
266     _styleSheetChanged: function(event)
267     {
268         if (this._isSettingContent)
269             return;
270
271         if (event.data.majorChange) {
272             this._updateStyleSheetText(event.data.styleSheetId);
273             return;
274         }
275
276         this._updateStyleSheetTextSoon(event.data.styleSheetId);
277     },
278
279     /**
280      * @param {!CSSAgent.StyleSheetId} styleSheetId
281      */
282     _updateStyleSheetTextSoon: function(styleSheetId)
283     {
284         if (this._updateStyleSheetTextTimer)
285             clearTimeout(this._updateStyleSheetTextTimer);
286
287         this._updateStyleSheetTextTimer = setTimeout(this._updateStyleSheetText.bind(this, styleSheetId), WebInspector.StylesSourceMapping.MinorChangeUpdateTimeoutMs);
288     },
289
290     /**
291      * @param {!CSSAgent.StyleSheetId} styleSheetId
292      */
293     _updateStyleSheetText: function(styleSheetId)
294     {
295         if (this._updateStyleSheetTextTimer) {
296             clearTimeout(this._updateStyleSheetTextTimer);
297             delete this._updateStyleSheetTextTimer;
298         }
299
300         var header = this._cssModel.styleSheetHeaderForId(styleSheetId);
301         if (!header)
302             return;
303         var styleSheetURL = header.resourceURL();
304         if (!styleSheetURL)
305             return;
306         var uiSourceCode = this._workspace.uiSourceCodeForURL(styleSheetURL)
307         if (!uiSourceCode)
308             return;
309         header.requestContent(callback.bind(this, uiSourceCode));
310
311         /**
312          * @param {!WebInspector.UISourceCode} uiSourceCode
313          * @param {?string} content
314          * @this {WebInspector.StylesSourceMapping}
315          */
316         function callback(uiSourceCode, content)
317         {
318             var styleFile = this._styleFiles.get(uiSourceCode);
319             if (styleFile)
320                 styleFile.addRevision(content || "");
321         }
322     }
323 }
324
325 /**
326  * @constructor
327  * @param {!WebInspector.UISourceCode} uiSourceCode
328  * @param {!WebInspector.StylesSourceMapping} mapping
329  */
330 WebInspector.StyleFile = function(uiSourceCode, mapping)
331 {
332     this._uiSourceCode = uiSourceCode;
333     this._mapping = mapping;
334     this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
335     this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
336     this._commitThrottler = new WebInspector.Throttler(WebInspector.StyleFile.updateTimeout);
337 }
338
339 WebInspector.StyleFile.updateTimeout = 200;
340
341 WebInspector.StyleFile.prototype = {
342     /**
343      * @param {!WebInspector.Event} event
344      */
345     _workingCopyCommitted: function(event)
346     {
347         if (this._isAddingRevision)
348             return;
349
350         this._isMajorChangePending = true;
351         this._commitThrottler.schedule(this._commitIncrementalEdit.bind(this), true);
352     },
353
354     /**
355      * @param {!WebInspector.Event} event
356      */
357     _workingCopyChanged: function(event)
358     {
359         if (this._isAddingRevision)
360             return;
361
362         this._commitThrottler.schedule(this._commitIncrementalEdit.bind(this), false);
363     },
364
365     /**
366      * @param {!WebInspector.Throttler.FinishCallback} finishCallback
367      */
368     _commitIncrementalEdit: function(finishCallback)
369     {
370         this._mapping._setStyleContent(this._uiSourceCode, this._uiSourceCode.workingCopy(), this._isMajorChangePending, this._styleContentSet.bind(this, finishCallback));
371         this._isMajorChangePending = false;
372     },
373
374     /**
375      * @param {!WebInspector.Throttler.FinishCallback} finishCallback
376      * @param {?string} error
377      */
378     _styleContentSet: function(finishCallback, error)
379     {
380         if (error)
381             WebInspector.console.error(error);
382         finishCallback();
383     },
384
385     /**
386      * @param {string} content
387      */
388     addRevision: function(content)
389     {
390         this._isAddingRevision = true;
391         this._uiSourceCode.addRevision(content);
392         delete this._isAddingRevision;
393     },
394
395     dispose: function()
396     {
397         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
398         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
399     }
400 }