Home | History | Annotate | Download | only in front_end
      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.ProjectWillReset, this._projectWillReset, this);
     42     this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAddedToWorkspace, this);
     43 
     44     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, this._mainFrameCreatedOrNavigated, this);
     45     this._initialize();
     46 }
     47 
     48 WebInspector.StylesSourceMapping.prototype = {
     49     /**
     50      * @param {WebInspector.RawLocation} rawLocation
     51      * @return {WebInspector.UILocation}
     52      */
     53     rawLocationToUILocation: function(rawLocation)
     54     {
     55         var location = /** @type WebInspector.CSSLocation */ (rawLocation);
     56         var uiSourceCode = this._workspace.uiSourceCodeForURL(location.url);
     57         if (!uiSourceCode)
     58             return null;
     59         return new WebInspector.UILocation(uiSourceCode, location.lineNumber, location.columnNumber);
     60     },
     61 
     62     /**
     63      * @param {WebInspector.UISourceCode} uiSourceCode
     64      * @param {number} lineNumber
     65      * @param {number} columnNumber
     66      * @return {WebInspector.RawLocation}
     67      */
     68     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
     69     {
     70         return new WebInspector.CSSLocation(uiSourceCode.url || "", lineNumber, columnNumber);
     71     },
     72 
     73     /**
     74      * @return {boolean}
     75      */
     76     isIdentity: function()
     77     {
     78         return true;
     79     },
     80 
     81     /**
     82      * @param {WebInspector.CSSStyleSheetHeader} header
     83      */
     84     addHeader: function(header)
     85     {
     86         var url = header.resourceURL();
     87         if (!url)
     88             return;
     89 
     90         header.pushSourceMapping(this);
     91         var map = this._urlToHeadersByFrameId[url];
     92         if (!map) {
     93             map = /** @type {!StringMap.<!StringMap.<!WebInspector.CSSStyleSheetHeader>>} */ (new StringMap());
     94             this._urlToHeadersByFrameId[url] = map;
     95         }
     96         var headersById = map.get(header.frameId);
     97         if (!headersById) {
     98             headersById = /** @type {!StringMap.<!WebInspector.CSSStyleSheetHeader>} */ (new StringMap());
     99             map.put(header.frameId, headersById);
    100         }
    101         headersById.put(header.id, header);
    102         var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
    103         if (uiSourceCode)
    104             this._bindUISourceCode(uiSourceCode, header);
    105     },
    106 
    107     /**
    108      * @param {WebInspector.CSSStyleSheetHeader} header
    109      */
    110     removeHeader: function(header)
    111     {
    112         var url = header.resourceURL();
    113         if (!url)
    114             return;
    115 
    116         var map = this._urlToHeadersByFrameId[url];
    117         console.assert(map);
    118         var headersById = map.get(header.frameId);
    119         console.assert(headersById);
    120         headersById.remove(header.id);
    121 
    122         if (!headersById.size()) {
    123             map.remove(header.frameId);
    124             if (!map.size()) {
    125                 delete this._urlToHeadersByFrameId[url];
    126                 var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
    127                 if (uiSourceCode)
    128                     this._unbindUISourceCode(uiSourceCode);
    129             }
    130         }
    131     },
    132 
    133     /**
    134      * @param {WebInspector.UISourceCode} uiSourceCode
    135      */
    136     _unbindUISourceCode: function(uiSourceCode)
    137     {
    138         if (uiSourceCode.styleFile()) {
    139             uiSourceCode.styleFile().dispose();
    140             uiSourceCode.setStyleFile(null);
    141         }
    142         uiSourceCode.setSourceMapping(null);
    143     },
    144 
    145     /**
    146      * @param {WebInspector.Event} event
    147      */
    148     _uiSourceCodeAddedToWorkspace: function(event)
    149     {
    150         var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.data);
    151         var url = uiSourceCode.url;
    152         if (!url || !this._urlToHeadersByFrameId[url])
    153             return;
    154         this._bindUISourceCode(uiSourceCode, this._urlToHeadersByFrameId[url].values()[0].values()[0]);
    155     },
    156 
    157     /**
    158      * @param {WebInspector.UISourceCode} uiSourceCode
    159      * @param {WebInspector.CSSStyleSheetHeader} header
    160      */
    161     _bindUISourceCode: function(uiSourceCode, header)
    162     {
    163         if (uiSourceCode.styleFile() || header.isInline)
    164             return;
    165         var url = uiSourceCode.url;
    166         uiSourceCode.setSourceMapping(this);
    167         uiSourceCode.setStyleFile(new WebInspector.StyleFile(uiSourceCode));
    168         header.updateLocations();
    169     },
    170 
    171     /**
    172      * @param {WebInspector.Event} event
    173      */
    174     _projectWillReset: function(event)
    175     {
    176         var project = /** @type {WebInspector.Project} */ (event.data);
    177         var uiSourceCodes = project.uiSourceCodes();
    178         for (var i = 0; i < uiSourceCodes; ++i)
    179             delete this._urlToHeadersByFrameId[uiSourceCodes[i].url];
    180     },
    181 
    182     _initialize: function()
    183     {
    184         /** @type {!Object.<string, !StringMap.<!StringMap.<!WebInspector.CSSStyleSheetHeader>>>} */
    185         this._urlToHeadersByFrameId = {};
    186     },
    187 
    188     /**
    189      * @param {WebInspector.Event} event
    190      */
    191     _mainFrameCreatedOrNavigated: function(event)
    192     {
    193         for (var url in this._urlToHeadersByFrameId) {
    194             var uiSourceCode = this._workspace.uiSourceCodeForURL(url);
    195             if (!uiSourceCode)
    196                 continue;
    197             this._unbindUISourceCode(uiSourceCode);
    198         }
    199         this._initialize();
    200     }
    201 }
    202 
    203 /**
    204  * @constructor
    205  * @param {WebInspector.UISourceCode} uiSourceCode
    206  */
    207 WebInspector.StyleFile = function(uiSourceCode)
    208 {
    209     this._uiSourceCode = uiSourceCode;
    210     this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
    211     this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
    212 }
    213 
    214 WebInspector.StyleFile.updateTimeout = 200;
    215 
    216 WebInspector.StyleFile.prototype = {
    217     _workingCopyCommitted: function(event)
    218     {
    219         if (this._isAddingRevision)
    220             return;
    221 
    222         this._commitIncrementalEdit(true);
    223     },
    224 
    225     _workingCopyChanged: function(event)
    226     {
    227         if (this._isAddingRevision)
    228             return;
    229 
    230         // FIXME: Extensions tests override updateTimeout because extensions don't have any control over applying changes to domain specific bindings.
    231         if (WebInspector.StyleFile.updateTimeout >= 0) {
    232             this._incrementalUpdateTimer = setTimeout(this._commitIncrementalEdit.bind(this, false), WebInspector.StyleFile.updateTimeout)
    233         } else
    234             this._commitIncrementalEdit(false);
    235     },
    236 
    237     /**
    238      * @param {boolean} majorChange
    239      */
    240     _commitIncrementalEdit: function(majorChange)
    241     {
    242         this._clearIncrementalUpdateTimer();
    243         WebInspector.styleContentBinding.setStyleContent(this._uiSourceCode, this._uiSourceCode.workingCopy(), majorChange, this._styleContentSet.bind(this));
    244     },
    245 
    246     /**
    247      * @param {?string} error
    248      */
    249     _styleContentSet: function(error)
    250     {
    251         if (error)
    252             WebInspector.showErrorMessage(error);
    253     },
    254 
    255     _clearIncrementalUpdateTimer: function()
    256     {
    257         if (!this._incrementalUpdateTimer)
    258             return;
    259         clearTimeout(this._incrementalUpdateTimer);
    260         delete this._incrementalUpdateTimer;
    261     },
    262 
    263     /**
    264      * @param {string} content
    265      */
    266     addRevision: function(content)
    267     {
    268         this._isAddingRevision = true;
    269         this._uiSourceCode.addRevision(content);
    270         delete this._isAddingRevision;
    271     },
    272 
    273     dispose: function()
    274     {
    275         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
    276         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
    277     }
    278 }
    279 
    280 /**
    281  * @constructor
    282  * @param {WebInspector.CSSStyleModel} cssModel
    283  */
    284 WebInspector.StyleContentBinding = function(cssModel, workspace)
    285 {
    286     this._cssModel = cssModel;
    287     this._workspace = workspace;
    288     this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetChanged, this);
    289 }
    290 
    291 WebInspector.StyleContentBinding.prototype = {
    292     /**
    293      * @param {WebInspector.UISourceCode} uiSourceCode
    294      * @param {string} content
    295      * @param {boolean} majorChange
    296      * @param {function(?string)} userCallback
    297      */
    298     setStyleContent: function(uiSourceCode, content, majorChange, userCallback)
    299     {
    300         var styleSheetIds = this._cssModel.styleSheetIdsForURL(uiSourceCode.url);
    301         if (!styleSheetIds.length) {
    302             userCallback("No stylesheet found: " + uiSourceCode.url);
    303             return;
    304         }
    305 
    306         this._isSettingContent = true;
    307         function callback(error)
    308         {
    309             userCallback(error);
    310             delete this._isSettingContent;
    311         }
    312         this._cssModel.setStyleSheetText(styleSheetIds[0], content, majorChange, callback.bind(this));
    313     },
    314 
    315     /**
    316      * @param {WebInspector.Event} event
    317      */
    318     _styleSheetChanged: function(event)
    319     {
    320         if (this._isSettingContent)
    321             return;
    322 
    323         if (!event.data.majorChange)
    324             return;
    325 
    326         /**
    327          * @param {?string} error
    328          * @param {string} content
    329          */
    330         function callback(error, content)
    331         {
    332             if (!error)
    333                 this._innerStyleSheetChanged(event.data.styleSheetId, content);
    334         }
    335         CSSAgent.getStyleSheetText(event.data.styleSheetId, callback.bind(this));
    336     },
    337 
    338     /**
    339      * @param {CSSAgent.StyleSheetId} styleSheetId
    340      * @param {string} content
    341      */
    342     _innerStyleSheetChanged: function(styleSheetId, content)
    343     {
    344         var header = this._cssModel.styleSheetHeaderForId(styleSheetId);
    345         if (!header)
    346             return;
    347         var styleSheetURL = header.resourceURL();
    348         if (!styleSheetURL)
    349             return;
    350 
    351         var uiSourceCode = this._workspace.uiSourceCodeForURL(styleSheetURL)
    352         if (!uiSourceCode)
    353             return;
    354 
    355         if (uiSourceCode.styleFile())
    356             uiSourceCode.styleFile().addRevision(content);
    357     }
    358 }
    359 
    360 /**
    361  * @type {?WebInspector.StyleContentBinding}
    362  */
    363 WebInspector.styleContentBinding = null;
    364