Home | History | Annotate | Download | only in bindings
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * @constructor
      7  * @implements {WebInspector.TargetManager.Observer}
      8  */
      9 WebInspector.CSSWorkspaceBinding = function()
     10 {
     11     /** @type {!Map.<!WebInspector.Target, !WebInspector.CSSWorkspaceBinding.TargetInfo>} */
     12     this._targetToTargetInfo = new Map();
     13     WebInspector.targetManager.observeTargets(this);
     14 
     15     WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameCreatedOrNavigated, this);
     16 }
     17 
     18 WebInspector.CSSWorkspaceBinding.prototype = {
     19     /**
     20      * @param {!WebInspector.Target} target
     21      */
     22     targetAdded: function(target)
     23     {
     24         this._targetToTargetInfo.set(target, new WebInspector.CSSWorkspaceBinding.TargetInfo(target, WebInspector.workspace, WebInspector.networkWorkspaceBinding));
     25     },
     26 
     27     /**
     28      * @param {!WebInspector.Target} target
     29      */
     30     targetRemoved: function(target)
     31     {
     32         this._targetToTargetInfo.remove(target)._dispose();
     33     },
     34 
     35     /**
     36      * @param {!WebInspector.CSSStyleSheetHeader} header
     37      * @param {!WebInspector.CSSSourceMapping} mapping
     38      */
     39     pushSourceMapping: function(header, mapping)
     40     {
     41         this._ensureInfoForHeader(header)._pushSourceMapping(mapping);
     42     },
     43 
     44     /**
     45      * @param {!WebInspector.CSSStyleSheetHeader} header
     46      * @return {?WebInspector.CSSWorkspaceBinding.HeaderInfo}
     47      */
     48     _headerInfo: function(header)
     49     {
     50         var map = this._targetToTargetInfo.get(header.target());
     51         return map._headerInfo(header.id) || null;
     52     },
     53 
     54     /**
     55      * @param {!WebInspector.CSSStyleSheetHeader} header
     56      * @return {!WebInspector.CSSWorkspaceBinding.HeaderInfo}
     57      */
     58     _ensureInfoForHeader: function(header)
     59     {
     60         var targetInfo = this._targetToTargetInfo.get(header.target());
     61         if (!targetInfo) {
     62             targetInfo = new WebInspector.CSSWorkspaceBinding.TargetInfo(header.target(), WebInspector.workspace, WebInspector.networkWorkspaceBinding);
     63             this._targetToTargetInfo.set(header.target(), targetInfo);
     64         }
     65         return targetInfo._ensureInfoForHeader(header);
     66     },
     67 
     68     /**
     69      * @param {!WebInspector.Event} event
     70      */
     71     _mainFrameCreatedOrNavigated: function(event)
     72     {
     73         var target = /** @type {!WebInspector.ResourceTreeModel} */ (event.target).target();
     74         this._targetToTargetInfo.get(target)._reset();
     75     },
     76 
     77     /**
     78      * @param {!WebInspector.CSSStyleSheetHeader} header
     79      */
     80     updateLocations: function(header)
     81     {
     82         var info = this._headerInfo(header);
     83         if (info)
     84             info._updateLocations();
     85     },
     86 
     87     /**
     88      * @param {!WebInspector.CSSLocation} rawLocation
     89      * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate
     90      * @return {!WebInspector.CSSWorkspaceBinding.LiveLocation}
     91      */
     92     createLiveLocation: function(rawLocation, updateDelegate)
     93     {
     94         var header = rawLocation.styleSheetId ? rawLocation.target().cssModel.styleSheetHeaderForId(rawLocation.styleSheetId) : null;
     95         return new WebInspector.CSSWorkspaceBinding.LiveLocation(rawLocation.target().cssModel, header, rawLocation, updateDelegate);
     96     },
     97 
     98     /**
     99      * @param {!WebInspector.CSSWorkspaceBinding.LiveLocation} location
    100      */
    101     _addLiveLocation: function(location)
    102     {
    103         this._ensureInfoForHeader(location._header)._addLocation(location);
    104     },
    105 
    106     /**
    107      * @param {!WebInspector.CSSWorkspaceBinding.LiveLocation} location
    108      */
    109     _removeLiveLocation: function(location)
    110     {
    111         var info = this._headerInfo(location._header);
    112         if (info)
    113             info._removeLocation(location);
    114     },
    115 
    116     /**
    117      * @param {!WebInspector.CSSProperty} cssProperty
    118      * @param {boolean} forName
    119      * @return {?WebInspector.UILocation}
    120      */
    121     propertyUILocation: function(cssProperty, forName)
    122     {
    123         var style = cssProperty.ownerStyle;
    124         if (!style || !style.parentRule || !style.styleSheetId)
    125             return null;
    126 
    127         var range = cssProperty.range;
    128         if (!range)
    129             return null;
    130 
    131         var url = style.parentRule.resourceURL();
    132         if (!url)
    133             return null;
    134 
    135         var line = forName ? range.startLine : range.endLine;
    136         // End of range is exclusive, so subtract 1 from the end offset.
    137         var column = forName ? range.startColumn : range.endColumn - (cssProperty.text && cssProperty.text.endsWith(";") ? 2 : 1);
    138         var rawLocation = new WebInspector.CSSLocation(style.target(), style.styleSheetId, url, line, column);
    139         return this.rawLocationToUILocation(rawLocation);
    140     },
    141 
    142     /**
    143      * @param {?WebInspector.CSSLocation} rawLocation
    144      * @return {?WebInspector.UILocation}
    145      */
    146     rawLocationToUILocation: function(rawLocation)
    147     {
    148         if (!rawLocation)
    149             return null;
    150         var cssModel = rawLocation.target().cssModel;
    151         var frameIdToSheetIds = cssModel.styleSheetIdsByFrameIdForURL(rawLocation.url);
    152         if (!Object.values(frameIdToSheetIds).length)
    153             return null;
    154         var styleSheetIds = [];
    155         for (var frameId in frameIdToSheetIds)
    156             styleSheetIds = styleSheetIds.concat(frameIdToSheetIds[frameId]);
    157         var uiLocation;
    158         for (var i = 0; !uiLocation && i < styleSheetIds.length; ++i) {
    159             var header = cssModel.styleSheetHeaderForId(styleSheetIds[i]);
    160             if (!header)
    161                 continue;
    162             var info = this._headerInfo(header);
    163             if (info)
    164                 uiLocation = info._rawLocationToUILocation(rawLocation.lineNumber, rawLocation.columnNumber);
    165         }
    166         return uiLocation || null;
    167     }
    168 }
    169 
    170 /**
    171  * @constructor
    172  * @param {!WebInspector.Target} target
    173  * @param {!WebInspector.Workspace} workspace
    174  * @param {!WebInspector.NetworkWorkspaceBinding} networkWorkspaceBinding
    175  */
    176 WebInspector.CSSWorkspaceBinding.TargetInfo = function(target, workspace, networkWorkspaceBinding)
    177 {
    178     this._target = target;
    179     this._workspace = workspace;
    180 
    181     var cssModel = target.cssModel;
    182     this._stylesSourceMapping = new WebInspector.StylesSourceMapping(cssModel, workspace);
    183     this._sassSourceMapping = new WebInspector.SASSSourceMapping(cssModel, workspace, networkWorkspaceBinding);
    184 
    185     /** @type {!StringMap.<!WebInspector.CSSWorkspaceBinding.HeaderInfo>} */
    186     this._headerInfoById = new StringMap();
    187 
    188     cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this);
    189     cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this);
    190 }
    191 
    192 WebInspector.CSSWorkspaceBinding.TargetInfo.prototype = {
    193     /**
    194      * @param {!WebInspector.Event} event
    195      */
    196     _styleSheetAdded: function(event)
    197     {
    198         var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.data);
    199         this._stylesSourceMapping.addHeader(header);
    200         this._sassSourceMapping.addHeader(header);
    201     },
    202 
    203     /**
    204      * @param {!WebInspector.Event} event
    205      */
    206     _styleSheetRemoved: function(event)
    207     {
    208         var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.data);
    209         this._stylesSourceMapping.removeHeader(header);
    210         this._sassSourceMapping.removeHeader(header);
    211         this._headerInfoById.remove(header.id);
    212     },
    213 
    214     /**
    215      * @param {!CSSAgent.StyleSheetId} id
    216      */
    217     _headerInfo: function(id)
    218     {
    219         return this._headerInfoById.get(id);
    220     },
    221 
    222     _ensureInfoForHeader: function(header)
    223     {
    224         var info = this._headerInfoById.get(header.id);
    225         if (!info) {
    226             info = new WebInspector.CSSWorkspaceBinding.HeaderInfo(header);
    227             this._headerInfoById.set(header.id, info);
    228         }
    229         return info;
    230     },
    231 
    232     _dispose: function()
    233     {
    234         this._reset();
    235         this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this);
    236         this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this);
    237     },
    238 
    239     _reset: function()
    240     {
    241         this._headerInfoById.clear();
    242     }
    243 }
    244 
    245 /**
    246  * @constructor
    247  * @param {!WebInspector.CSSStyleSheetHeader} header
    248  */
    249 WebInspector.CSSWorkspaceBinding.HeaderInfo = function(header)
    250 {
    251     this._header = header;
    252 
    253     /** @type {!Array.<!WebInspector.CSSSourceMapping>} */
    254     this._sourceMappings = [];
    255 
    256     /** @type {!Set.<!WebInspector.LiveLocation>} */
    257     this._locations = new Set();
    258 }
    259 
    260 WebInspector.CSSWorkspaceBinding.HeaderInfo.prototype = {
    261     /**
    262      * @param {!WebInspector.LiveLocation} location
    263      */
    264     _addLocation: function(location)
    265     {
    266         this._locations.add(location);
    267         location.update();
    268     },
    269 
    270     /**
    271      * @param {!WebInspector.LiveLocation} location
    272      */
    273     _removeLocation: function(location)
    274     {
    275         this._locations.remove(location);
    276     },
    277 
    278     _updateLocations: function()
    279     {
    280         var items = this._locations.values();
    281         for (var i = 0; i < items.length; ++i)
    282             items[i].update();
    283     },
    284 
    285     /**
    286      * @param {number} lineNumber
    287      * @param {number=} columnNumber
    288      * @return {?WebInspector.UILocation}
    289      */
    290     _rawLocationToUILocation: function(lineNumber, columnNumber)
    291     {
    292         var uiLocation = null;
    293         var rawLocation = new WebInspector.CSSLocation(this._header.target(), this._header.id, this._header.resourceURL(), lineNumber, columnNumber);
    294         for (var i = this._sourceMappings.length - 1; !uiLocation && i >= 0; --i)
    295             uiLocation = this._sourceMappings[i].rawLocationToUILocation(rawLocation);
    296         return uiLocation;
    297     },
    298 
    299     /**
    300      * @param {!WebInspector.CSSSourceMapping} sourceMapping
    301      */
    302     _pushSourceMapping: function(sourceMapping)
    303     {
    304         this._sourceMappings.push(sourceMapping);
    305         this._updateLocations();
    306     }
    307 }
    308 
    309 /**
    310  * @constructor
    311  * @extends {WebInspector.LiveLocation}
    312  * @param {!WebInspector.CSSStyleModel} cssModel
    313  * @param {?WebInspector.CSSStyleSheetHeader} header
    314  * @param {!WebInspector.CSSLocation} rawLocation
    315  * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate
    316  */
    317 WebInspector.CSSWorkspaceBinding.LiveLocation = function(cssModel, header, rawLocation, updateDelegate)
    318 {
    319     WebInspector.LiveLocation.call(this, updateDelegate);
    320     this._cssModel = cssModel;
    321     this._rawLocation = rawLocation;
    322     if (!header)
    323         this._clearStyleSheet();
    324     else
    325         this._setStyleSheet(header);
    326 }
    327 
    328 WebInspector.CSSWorkspaceBinding.LiveLocation.prototype = {
    329     /**
    330      * @param {!WebInspector.Event} event
    331      */
    332     _styleSheetAdded: function(event)
    333     {
    334         console.assert(!this._header);
    335         var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.data);
    336         if (header.sourceURL && header.sourceURL === this._rawLocation.url)
    337             this._setStyleSheet(header);
    338     },
    339 
    340     /**
    341      * @param {!WebInspector.Event} event
    342      */
    343     _styleSheetRemoved: function(event)
    344     {
    345         console.assert(this._header);
    346         var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.data);
    347         if (this._header !== header)
    348             return;
    349         WebInspector.cssWorkspaceBinding._removeLiveLocation(this);
    350         this._clearStyleSheet();
    351     },
    352 
    353     /**
    354      * @param {!WebInspector.CSSStyleSheetHeader} header
    355      */
    356     _setStyleSheet: function(header)
    357     {
    358         this._header = header;
    359         WebInspector.cssWorkspaceBinding._addLiveLocation(this);
    360         this._cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this);
    361         this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this);
    362     },
    363 
    364     _clearStyleSheet: function()
    365     {
    366         delete this._header;
    367         this._cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this);
    368         this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this);
    369     },
    370 
    371     /**
    372      * @return {?WebInspector.UILocation}
    373      */
    374     uiLocation: function()
    375     {
    376         var cssLocation = this._rawLocation;
    377         if (this._header) {
    378             var headerInfo = WebInspector.cssWorkspaceBinding._headerInfo(this._header);
    379             return headerInfo._rawLocationToUILocation(cssLocation.lineNumber, cssLocation.columnNumber);
    380         }
    381         var uiSourceCode = WebInspector.workspace.uiSourceCodeForURL(cssLocation.url);
    382         if (!uiSourceCode)
    383             return null;
    384         return uiSourceCode.uiLocation(cssLocation.lineNumber, cssLocation.columnNumber);
    385     },
    386 
    387     dispose: function()
    388     {
    389         WebInspector.LiveLocation.prototype.dispose.call(this);
    390         if (this._header)
    391             WebInspector.cssWorkspaceBinding._removeLiveLocation(this);
    392         this._cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this);
    393         this._cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this);
    394     },
    395 
    396     __proto__: WebInspector.LiveLocation.prototype
    397 }
    398 
    399 /**
    400  * @interface
    401  */
    402 WebInspector.CSSSourceMapping = function()
    403 {
    404 }
    405 
    406 WebInspector.CSSSourceMapping.prototype = {
    407     /**
    408      * @param {!WebInspector.CSSLocation} rawLocation
    409      * @return {?WebInspector.UILocation}
    410      */
    411     rawLocationToUILocation: function(rawLocation) { },
    412 
    413     /**
    414      * @param {!WebInspector.UISourceCode} uiSourceCode
    415      * @param {number} lineNumber
    416      * @param {number} columnNumber
    417      * @return {?WebInspector.CSSLocation}
    418      */
    419     uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) { },
    420 
    421     /**
    422      * @return {boolean}
    423      */
    424     isIdentity: function() { },
    425 
    426     /**
    427      * @param {!WebInspector.UISourceCode} uiSourceCode
    428      * @param {number} lineNumber
    429      * @return {boolean}
    430      */
    431     uiLineHasMapping: function(uiSourceCode, lineNumber) { }
    432 }
    433 
    434 /**
    435  * @type {!WebInspector.CSSWorkspaceBinding}
    436  */
    437 WebInspector.cssWorkspaceBinding;
    438