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