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