Home | History | Annotate | Download | only in bindings
      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     cssModel.target().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.set(header.frameId, headersById);
    123         }
    124         headersById.set(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.set(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 }
    401