Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / bindings / StylesSourceMapping.js
blobba4edd05f0eedd831438e44bf6d907525df87a43
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  */
31 /**
32  * @constructor
33  * @implements {WebInspector.CSSSourceMapping}
34  * @param {!WebInspector.CSSStyleModel} cssModel
35  * @param {!WebInspector.Workspace} workspace
36  * @param {!WebInspector.NetworkMapping} networkMapping
37  */
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);
50     this._initialize();
53 WebInspector.StylesSourceMapping.ChangeUpdateTimeoutMs = 200;
55 WebInspector.StylesSourceMapping.prototype = {
56     /**
57      * @override
58      * @param {!WebInspector.CSSLocation} rawLocation
59      * @return {?WebInspector.UILocation}
60      */
61     rawLocationToUILocation: function(rawLocation)
62     {
63         var uiSourceCode = this._networkMapping.uiSourceCodeForURL(rawLocation.url, rawLocation.target());
64         if (!uiSourceCode)
65             return null;
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);
72         }
73         return uiSourceCode.uiLocation(lineNumber, columnNumber);
74     },
76     /**
77      * @override
78      * @param {!WebInspector.UISourceCode} uiSourceCode
79      * @param {number} lineNumber
80      * @param {number} columnNumber
81      * @return {?WebInspector.CSSLocation}
82      */
83     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
84     {
85         return null;
86     },
88     /**
89      * @override
90      * @return {boolean}
91      */
92     isIdentity: function()
93     {
94         return true;
95     },
97     /**
98      * @override
99      * @param {!WebInspector.UISourceCode} uiSourceCode
100      * @param {number} lineNumber
101      * @return {boolean}
102      */
103     uiLineHasMapping: function(uiSourceCode, lineNumber)
104     {
105         return true;
106     },
108     /**
109      * @return {!WebInspector.Target}
110      */
111     target: function()
112     {
113         return this._cssModel.target();
114     },
116     /**
117      * @param {!WebInspector.CSSStyleSheetHeader} header
118      */
119     addHeader: function(header)
120     {
121         var url = header.resourceURL();
122         if (!url)
123             return;
125         WebInspector.cssWorkspaceBinding.pushSourceMapping(header, this);
126         var map = this._urlToHeadersByFrameId[url];
127         if (!map) {
128             map = /** @type {!Map.<string, !Map.<string, !WebInspector.CSSStyleSheetHeader>>} */ (new Map());
129             this._urlToHeadersByFrameId[url] = map;
130         }
131         var headersById = map.get(header.frameId);
132         if (!headersById) {
133             headersById = /** @type {!Map.<string, !WebInspector.CSSStyleSheetHeader>} */ (new Map());
134             map.set(header.frameId, headersById);
135         }
136         headersById.set(header.id, header);
137         var uiSourceCode = this._networkMapping.uiSourceCodeForURL(url, header.target());
138         if (uiSourceCode)
139             this._bindUISourceCode(uiSourceCode, header);
140     },
142     /**
143      * @param {!WebInspector.CSSStyleSheetHeader} header
144      */
145     removeHeader: function(header)
146     {
147         var url = header.resourceURL();
148         if (!url)
149             return;
151         var map = this._urlToHeadersByFrameId[url];
152         console.assert(map);
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);
159             if (!map.size) {
160                 delete this._urlToHeadersByFrameId[url];
161                 var uiSourceCode = this._networkMapping.uiSourceCodeForURL(url, header.target());
162                 if (uiSourceCode)
163                     this._unbindUISourceCode(uiSourceCode);
164             }
165         }
166     },
168     /**
169      * @param {!WebInspector.UISourceCode} uiSourceCode
170      */
171     _unbindUISourceCode: function(uiSourceCode)
172     {
173         var styleFile = this._styleFiles.get(uiSourceCode);
174         if (!styleFile)
175             return;
176         styleFile.dispose();
177         this._styleFiles.remove(uiSourceCode);
178     },
180     /**
181      * @param {!WebInspector.Event} event
182      */
183     _uiSourceCodeAddedToWorkspace: function(event)
184     {
185         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
186         var networkURL = this._networkMapping.networkURL(uiSourceCode);
187         if (!networkURL || !this._urlToHeadersByFrameId[networkURL])
188             return;
189         this._bindUISourceCode(uiSourceCode, this._urlToHeadersByFrameId[networkURL].valuesArray()[0].valuesArray()[0]);
190     },
192     /**
193      * @param {!WebInspector.UISourceCode} uiSourceCode
194      * @param {!WebInspector.CSSStyleSheetHeader} header
195      */
196     _bindUISourceCode: function(uiSourceCode, header)
197     {
198         if (this._styleFiles.get(uiSourceCode) || (header.isInline && !header.hasSourceURL))
199             return;
200         this._styleFiles.set(uiSourceCode, new WebInspector.StyleFile(uiSourceCode, this));
201         WebInspector.cssWorkspaceBinding.updateLocations(header);
202     },
204     /**
205      * @param {!WebInspector.Event} event
206      */
207     _projectRemoved: function(event)
208     {
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]);
213     },
215     /**
216      * @param {!WebInspector.Event} event
217      */
218     _uiSourceCodeRemoved: function(event)
219     {
220         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
221         this._unbindUISourceCode(uiSourceCode);
222     },
224     _initialize: function()
225     {
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();
230     },
232     /**
233      * @param {!WebInspector.Event} event
234      */
235     _mainFrameNavigated: function(event)
236     {
237         for (var url in this._urlToHeadersByFrameId) {
238             var uiSourceCode = this._networkMapping.uiSourceCodeForURL(url, this._cssModel.target());
239             if (!uiSourceCode)
240                 continue;
241             this._unbindUISourceCode(uiSourceCode);
242         }
243         this._initialize();
244     },
246     /**
247      * @param {!WebInspector.UISourceCode} uiSourceCode
248      * @param {string} content
249      * @param {boolean} majorChange
250      * @return {!Promise<?string>}
251      */
252     _setStyleContent: function(uiSourceCode, content, majorChange)
253     {
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;
261         /**
262          * @param {?string} error
263          * @this {WebInspector.StylesSourceMapping}
264          * @return {?string}
265          */
266         function callback(error)
267         {
268             delete this._isSettingContent;
269             return error || null;
270         }
272         var promises = [];
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));
277     },
279     /**
280      * @param {!WebInspector.Event} event
281      */
282     _styleSheetChanged: function(event)
283     {
284         if (this._isSettingContent)
285             return;
287         this._updateStyleSheetTextSoon(event.data.styleSheetId);
288     },
290     /**
291      * @param {!CSSAgent.StyleSheetId} styleSheetId
292      */
293     _updateStyleSheetTextSoon: function(styleSheetId)
294     {
295         if (this._updateStyleSheetTextTimer)
296             clearTimeout(this._updateStyleSheetTextTimer);
298         this._updateStyleSheetTextTimer = setTimeout(this._updateStyleSheetText.bind(this, styleSheetId), WebInspector.StylesSourceMapping.ChangeUpdateTimeoutMs);
299     },
301     /**
302      * @param {!CSSAgent.StyleSheetId} styleSheetId
303      */
304     _updateStyleSheetText: function(styleSheetId)
305     {
306         if (this._updateStyleSheetTextTimer) {
307             clearTimeout(this._updateStyleSheetTextTimer);
308             delete this._updateStyleSheetTextTimer;
309         }
311         var header = this._cssModel.styleSheetHeaderForId(styleSheetId);
312         if (!header)
313             return;
314         var styleSheetURL = header.resourceURL();
315         if (!styleSheetURL)
316             return;
317         var uiSourceCode = this._networkMapping.uiSourceCodeForURL(styleSheetURL, header.target());
318         if (!uiSourceCode)
319             return;
320         header.requestContent(callback.bind(this, uiSourceCode));
322         /**
323          * @param {!WebInspector.UISourceCode} uiSourceCode
324          * @param {?string} content
325          * @this {WebInspector.StylesSourceMapping}
326          */
327         function callback(uiSourceCode, content)
328         {
329             var styleFile = this._styleFiles.get(uiSourceCode);
330             if (styleFile)
331                 styleFile.addRevision(content || "");
332         }
333     }
337  * @constructor
338  * @param {!WebInspector.UISourceCode} uiSourceCode
339  * @param {!WebInspector.StylesSourceMapping} mapping
340  */
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 = {
353     /**
354      * @param {!WebInspector.Event} event
355      */
356     _workingCopyCommitted: function(event)
357     {
358         if (this._isAddingRevision)
359             return;
361         this._isMajorChangePending = true;
362         this._commitThrottler.schedule(this._commitIncrementalEdit.bind(this), true);
363     },
365     /**
366      * @param {!WebInspector.Event} event
367      */
368     _workingCopyChanged: function(event)
369     {
370         if (this._isAddingRevision)
371             return;
373         this._commitThrottler.schedule(this._commitIncrementalEdit.bind(this), false);
374     },
376     _commitIncrementalEdit: function()
377     {
378         var promise = this._mapping._setStyleContent(this._uiSourceCode, this._uiSourceCode.workingCopy(), this._isMajorChangePending)
379             .then(this._styleContentSet.bind(this))
380         this._isMajorChangePending = false;
381         return promise;
382     },
384     /**
385      * @param {?string} error
386      */
387     _styleContentSet: function(error)
388     {
389         if (error)
390             WebInspector.console.error(error);
391     },
393     /**
394      * @param {string} content
395      */
396     addRevision: function(content)
397     {
398         this._isAddingRevision = true;
399         this._uiSourceCode.addRevision(content);
400         delete this._isAddingRevision;
401     },
403     dispose: function()
404     {
405         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
406         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
407     }