Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2010 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  * @extends {WebInspector.Object}
     34  * @param {WebInspector.Workspace} workspace
     35  */
     36 WebInspector.CSSStyleModel = function(workspace)
     37 {
     38     this._workspace = workspace;
     39     this._pendingCommandsMajorState = [];
     40     WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.UndoRedoRequested, this._undoRedoRequested, this);
     41     WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.UndoRedoCompleted, this._undoRedoCompleted, this);
     42     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, this._mainFrameCreatedOrNavigated, this);
     43     this._namedFlowCollections = {};
     44     WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.DocumentUpdated, this._resetNamedFlowCollections, this);
     45     InspectorBackend.registerCSSDispatcher(new WebInspector.CSSDispatcher(this));
     46     CSSAgent.enable();
     47     this._resetStyleSheets();
     48 }
     49 
     50 /**
     51  * @param {Array.<CSSAgent.CSSRule>} ruleArray
     52  */
     53 WebInspector.CSSStyleModel.parseRuleArrayPayload = function(ruleArray)
     54 {
     55     var result = [];
     56     for (var i = 0; i < ruleArray.length; ++i)
     57         result.push(WebInspector.CSSRule.parsePayload(ruleArray[i]));
     58     return result;
     59 }
     60 
     61 /**
     62  * @param {Array.<CSSAgent.RuleMatch>} matchArray
     63  */
     64 WebInspector.CSSStyleModel.parseRuleMatchArrayPayload = function(matchArray)
     65 {
     66     var result = [];
     67     for (var i = 0; i < matchArray.length; ++i)
     68         result.push(WebInspector.CSSRule.parsePayload(matchArray[i].rule, matchArray[i].matchingSelectors));
     69     return result;
     70 }
     71 
     72 WebInspector.CSSStyleModel.Events = {
     73     StyleSheetAdded: "StyleSheetAdded",
     74     StyleSheetChanged: "StyleSheetChanged",
     75     StyleSheetRemoved: "StyleSheetRemoved",
     76     MediaQueryResultChanged: "MediaQueryResultChanged",
     77     NamedFlowCreated: "NamedFlowCreated",
     78     NamedFlowRemoved: "NamedFlowRemoved",
     79     RegionLayoutUpdated: "RegionLayoutUpdated",
     80     RegionOversetChanged: "RegionOversetChanged"
     81 }
     82 
     83 WebInspector.CSSStyleModel.MediaTypes = ["all", "braille", "embossed", "handheld", "print", "projection", "screen", "speech", "tty", "tv"];
     84 
     85 WebInspector.CSSStyleModel.prototype = {
     86     /**
     87      * @param {DOMAgent.NodeId} nodeId
     88      * @param {boolean} needPseudo
     89      * @param {boolean} needInherited
     90      * @param {function(?*)} userCallback
     91      */
     92     getMatchedStylesAsync: function(nodeId, needPseudo, needInherited, userCallback)
     93     {
     94         /**
     95          * @param {function(?*)} userCallback
     96          * @param {?Protocol.Error} error
     97          * @param {Array.<CSSAgent.RuleMatch>=} matchedPayload
     98          * @param {Array.<CSSAgent.PseudoIdMatches>=} pseudoPayload
     99          * @param {Array.<CSSAgent.InheritedStyleEntry>=} inheritedPayload
    100          */
    101         function callback(userCallback, error, matchedPayload, pseudoPayload, inheritedPayload)
    102         {
    103             if (error) {
    104                 if (userCallback)
    105                     userCallback(null);
    106                 return;
    107             }
    108 
    109             var result = {};
    110             if (matchedPayload)
    111                 result.matchedCSSRules = WebInspector.CSSStyleModel.parseRuleMatchArrayPayload(matchedPayload);
    112 
    113             if (pseudoPayload) {
    114                 result.pseudoElements = [];
    115                 for (var i = 0; i < pseudoPayload.length; ++i) {
    116                     var entryPayload = pseudoPayload[i];
    117                     result.pseudoElements.push({ pseudoId: entryPayload.pseudoId, rules: WebInspector.CSSStyleModel.parseRuleMatchArrayPayload(entryPayload.matches) });
    118                 }
    119             }
    120 
    121             if (inheritedPayload) {
    122                 result.inherited = [];
    123                 for (var i = 0; i < inheritedPayload.length; ++i) {
    124                     var entryPayload = inheritedPayload[i];
    125                     var entry = {};
    126                     if (entryPayload.inlineStyle)
    127                         entry.inlineStyle = WebInspector.CSSStyleDeclaration.parsePayload(entryPayload.inlineStyle);
    128                     if (entryPayload.matchedCSSRules)
    129                         entry.matchedCSSRules = WebInspector.CSSStyleModel.parseRuleMatchArrayPayload(entryPayload.matchedCSSRules);
    130                     result.inherited.push(entry);
    131                 }
    132             }
    133 
    134             if (userCallback)
    135                 userCallback(result);
    136         }
    137 
    138         CSSAgent.getMatchedStylesForNode(nodeId, needPseudo, needInherited, callback.bind(null, userCallback));
    139     },
    140 
    141     /**
    142      * @param {DOMAgent.NodeId} nodeId
    143      * @param {function(?WebInspector.CSSStyleDeclaration)} userCallback
    144      */
    145     getComputedStyleAsync: function(nodeId, userCallback)
    146     {
    147         /**
    148          * @param {function(?WebInspector.CSSStyleDeclaration)} userCallback
    149          */
    150         function callback(userCallback, error, computedPayload)
    151         {
    152             if (error || !computedPayload)
    153                 userCallback(null);
    154             else
    155                 userCallback(WebInspector.CSSStyleDeclaration.parseComputedStylePayload(computedPayload));
    156         }
    157 
    158         CSSAgent.getComputedStyleForNode(nodeId, callback.bind(null, userCallback));
    159     },
    160 
    161     /**
    162      * @param {DOMAgent.NodeId} nodeId
    163      * @param {function(?WebInspector.CSSStyleDeclaration, ?WebInspector.CSSStyleDeclaration)} userCallback
    164      */
    165     getInlineStylesAsync: function(nodeId, userCallback)
    166     {
    167         /**
    168          * @param {function(?WebInspector.CSSStyleDeclaration, ?WebInspector.CSSStyleDeclaration)} userCallback
    169          * @param {?Protocol.Error} error
    170          * @param {?CSSAgent.CSSStyle=} inlinePayload
    171          * @param {?CSSAgent.CSSStyle=} attributesStylePayload
    172          */
    173         function callback(userCallback, error, inlinePayload, attributesStylePayload)
    174         {
    175             if (error || !inlinePayload)
    176                 userCallback(null, null);
    177             else
    178                 userCallback(WebInspector.CSSStyleDeclaration.parsePayload(inlinePayload), attributesStylePayload ? WebInspector.CSSStyleDeclaration.parsePayload(attributesStylePayload) : null);
    179         }
    180 
    181         CSSAgent.getInlineStylesForNode(nodeId, callback.bind(null, userCallback));
    182     },
    183 
    184     /**
    185      * @param {DOMAgent.NodeId} nodeId
    186      * @param {?Array.<string>|undefined} forcedPseudoClasses
    187      * @param {function()=} userCallback
    188      */
    189     forcePseudoState: function(nodeId, forcedPseudoClasses, userCallback)
    190     {
    191         CSSAgent.forcePseudoState(nodeId, forcedPseudoClasses || [], userCallback);
    192     },
    193 
    194     /**
    195      * @param {DOMAgent.NodeId} documentNodeId
    196      * @param {function(?WebInspector.NamedFlowCollection)} userCallback
    197      */
    198     getNamedFlowCollectionAsync: function(documentNodeId, userCallback)
    199     {
    200         var namedFlowCollection = this._namedFlowCollections[documentNodeId];
    201         if (namedFlowCollection) {
    202             userCallback(namedFlowCollection);
    203             return;
    204         }
    205 
    206         /**
    207          * @param {function(?WebInspector.NamedFlowCollection)} userCallback
    208          * @param {?Protocol.Error} error
    209          * @param {?Array.<CSSAgent.NamedFlow>} namedFlowPayload
    210          */
    211         function callback(userCallback, error, namedFlowPayload)
    212         {
    213             if (error || !namedFlowPayload)
    214                 userCallback(null);
    215             else {
    216                 var namedFlowCollection = new WebInspector.NamedFlowCollection(namedFlowPayload);
    217                 this._namedFlowCollections[documentNodeId] = namedFlowCollection;
    218                 userCallback(namedFlowCollection);
    219             }
    220         }
    221 
    222         CSSAgent.getNamedFlowCollection(documentNodeId, callback.bind(this, userCallback));
    223     },
    224 
    225     /**
    226      * @param {DOMAgent.NodeId} documentNodeId
    227      * @param {string} flowName
    228      * @param {function(?WebInspector.NamedFlow)} userCallback
    229      */
    230     getFlowByNameAsync: function(documentNodeId, flowName, userCallback)
    231     {
    232         var namedFlowCollection = this._namedFlowCollections[documentNodeId];
    233         if (namedFlowCollection) {
    234             userCallback(namedFlowCollection.flowByName(flowName));
    235             return;
    236         }
    237 
    238         /**
    239          * @param {function(?WebInspector.NamedFlow)} userCallback
    240          * @param {?WebInspector.NamedFlowCollection} namedFlowCollection
    241          */
    242         function callback(userCallback, namedFlowCollection)
    243         {
    244             if (!namedFlowCollection)
    245                 userCallback(null);
    246             else
    247                 userCallback(namedFlowCollection.flowByName(flowName));
    248         }
    249 
    250         this.getNamedFlowCollectionAsync(documentNodeId, callback.bind(this, userCallback));
    251     },
    252 
    253     /**
    254      * @param {CSSAgent.CSSRuleId} ruleId
    255      * @param {DOMAgent.NodeId} nodeId
    256      * @param {string} newSelector
    257      * @param {function(WebInspector.CSSRule, boolean)} successCallback
    258      * @param {function()} failureCallback
    259      */
    260     setRuleSelector: function(ruleId, nodeId, newSelector, successCallback, failureCallback)
    261     {
    262         /**
    263          * @param {DOMAgent.NodeId} nodeId
    264          * @param {function(WebInspector.CSSRule, boolean)} successCallback
    265          * @param {CSSAgent.CSSRule} rulePayload
    266          * @param {?Array.<DOMAgent.NodeId>} selectedNodeIds
    267          */
    268         function checkAffectsCallback(nodeId, successCallback, rulePayload, selectedNodeIds)
    269         {
    270             if (!selectedNodeIds)
    271                 return;
    272             var doesAffectSelectedNode = (selectedNodeIds.indexOf(nodeId) >= 0);
    273             var rule = WebInspector.CSSRule.parsePayload(rulePayload);
    274             successCallback(rule, doesAffectSelectedNode);
    275         }
    276 
    277         /**
    278          * @param {DOMAgent.NodeId} nodeId
    279          * @param {function(WebInspector.CSSRule, boolean)} successCallback
    280          * @param {function()} failureCallback
    281          * @param {?Protocol.Error} error
    282          * @param {string} newSelector
    283          * @param {?CSSAgent.CSSRule} rulePayload
    284          */
    285         function callback(nodeId, successCallback, failureCallback, newSelector, error, rulePayload)
    286         {
    287             this._pendingCommandsMajorState.pop();
    288             if (error)
    289                 failureCallback();
    290             else {
    291                 WebInspector.domAgent.markUndoableState();
    292                 var ownerDocumentId = this._ownerDocumentId(nodeId);
    293                 if (ownerDocumentId)
    294                     WebInspector.domAgent.querySelectorAll(ownerDocumentId, newSelector, checkAffectsCallback.bind(this, nodeId, successCallback, rulePayload));
    295                 else
    296                     failureCallback();
    297             }
    298         }
    299 
    300         this._pendingCommandsMajorState.push(true);
    301         CSSAgent.setRuleSelector(ruleId, newSelector, callback.bind(this, nodeId, successCallback, failureCallback, newSelector));
    302     },
    303 
    304     /**
    305      * @param {DOMAgent.NodeId} nodeId
    306      * @param {string} selector
    307      * @param {function(WebInspector.CSSRule, boolean)} successCallback
    308      * @param {function()} failureCallback
    309      */
    310     addRule: function(nodeId, selector, successCallback, failureCallback)
    311     {
    312         /**
    313          * @param {DOMAgent.NodeId} nodeId
    314          * @param {function(WebInspector.CSSRule, boolean)} successCallback
    315          * @param {CSSAgent.CSSRule} rulePayload
    316          * @param {?Array.<DOMAgent.NodeId>} selectedNodeIds
    317          */
    318         function checkAffectsCallback(nodeId, successCallback, rulePayload, selectedNodeIds)
    319         {
    320             if (!selectedNodeIds)
    321                 return;
    322 
    323             var doesAffectSelectedNode = (selectedNodeIds.indexOf(nodeId) >= 0);
    324             var rule = WebInspector.CSSRule.parsePayload(rulePayload);
    325             successCallback(rule, doesAffectSelectedNode);
    326         }
    327 
    328         /**
    329          * @param {function(WebInspector.CSSRule, boolean)} successCallback
    330          * @param {function()} failureCallback
    331          * @param {string} selector
    332          * @param {?Protocol.Error} error
    333          * @param {?CSSAgent.CSSRule} rulePayload
    334          */
    335         function callback(successCallback, failureCallback, selector, error, rulePayload)
    336         {
    337             this._pendingCommandsMajorState.pop();
    338             if (error) {
    339                 // Invalid syntax for a selector
    340                 failureCallback();
    341             } else {
    342                 WebInspector.domAgent.markUndoableState();
    343                 var ownerDocumentId = this._ownerDocumentId(nodeId);
    344                 if (ownerDocumentId)
    345                     WebInspector.domAgent.querySelectorAll(ownerDocumentId, selector, checkAffectsCallback.bind(this, nodeId, successCallback, rulePayload));
    346                 else
    347                     failureCallback();
    348             }
    349         }
    350 
    351         this._pendingCommandsMajorState.push(true);
    352         CSSAgent.addRule(nodeId, selector, callback.bind(this, successCallback, failureCallback, selector));
    353     },
    354 
    355     mediaQueryResultChanged: function()
    356     {
    357         this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged);
    358     },
    359 
    360     /**
    361      * @param {!CSSAgent.StyleSheetId} id
    362      * @return {WebInspector.CSSStyleSheetHeader}
    363      */
    364     styleSheetHeaderForId: function(id)
    365     {
    366         return this._styleSheetIdToHeader[id];
    367     },
    368 
    369     /**
    370      * @return {Array.<WebInspector.CSSStyleSheetHeader>}
    371      */
    372     styleSheetHeaders: function()
    373     {
    374         return Object.values(this._styleSheetIdToHeader);
    375     },
    376 
    377     /**
    378      * @param {DOMAgent.NodeId} nodeId
    379      */
    380     _ownerDocumentId: function(nodeId)
    381     {
    382         var node = WebInspector.domAgent.nodeForId(nodeId);
    383         if (!node)
    384             return null;
    385         return node.ownerDocument ? node.ownerDocument.id : null;
    386     },
    387 
    388     /**
    389      * @param {CSSAgent.StyleSheetId} styleSheetId
    390      */
    391     _fireStyleSheetChanged: function(styleSheetId)
    392     {
    393         if (!this._pendingCommandsMajorState.length)
    394             return;
    395 
    396         var majorChange = this._pendingCommandsMajorState[this._pendingCommandsMajorState.length - 1];
    397 
    398         if (!majorChange || !styleSheetId || !this.hasEventListeners(WebInspector.CSSStyleModel.Events.StyleSheetChanged))
    399             return;
    400 
    401         this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.StyleSheetChanged, { styleSheetId: styleSheetId, majorChange: majorChange });
    402     },
    403 
    404     /**
    405      * @param {!CSSAgent.CSSStyleSheetHeader} header
    406      */
    407     _styleSheetAdded: function(header)
    408     {
    409         console.assert(!this._styleSheetIdToHeader[header.styleSheetId]);
    410         var styleSheetHeader = new WebInspector.CSSStyleSheetHeader(header);
    411         this._styleSheetIdToHeader[header.styleSheetId] = styleSheetHeader;
    412         var url = styleSheetHeader.resourceURL();
    413         if (!this._styleSheetIdsForURL[url])
    414             this._styleSheetIdsForURL[url] = {};
    415         var frameIdToStyleSheetIds = this._styleSheetIdsForURL[url];
    416         var styleSheetIds = frameIdToStyleSheetIds[styleSheetHeader.frameId];
    417         if (!styleSheetIds) {
    418             styleSheetIds = [];
    419             frameIdToStyleSheetIds[styleSheetHeader.frameId] = styleSheetIds;
    420         }
    421         styleSheetIds.push(styleSheetHeader.id);
    422         this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.StyleSheetAdded, styleSheetHeader);
    423     },
    424 
    425     /**
    426      * @param {!CSSAgent.StyleSheetId} id
    427      */
    428     _styleSheetRemoved: function(id)
    429     {
    430         var header = this._styleSheetIdToHeader[id];
    431         console.assert(header);
    432         delete this._styleSheetIdToHeader[id];
    433         var url = header.resourceURL();
    434         var frameIdToStyleSheetIds = this._styleSheetIdsForURL[url];
    435         frameIdToStyleSheetIds[header.frameId].remove(id);
    436         if (!frameIdToStyleSheetIds[header.frameId].length) {
    437             delete frameIdToStyleSheetIds[header.frameId];
    438             if (!Object.keys(this._styleSheetIdsForURL[url]).length)
    439                 delete this._styleSheetIdsForURL[url];
    440         }
    441         this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, header);
    442     },
    443 
    444     /**
    445      * @param {string} url
    446      * @return {Array.<CSSAgent.StyleSheetId>}
    447      */
    448     styleSheetIdsForURL: function(url)
    449     {
    450         var frameIdToStyleSheetIds = this._styleSheetIdsForURL[url];
    451         if (!frameIdToStyleSheetIds)
    452             return [];
    453 
    454         var result = [];
    455         for (var frameId in frameIdToStyleSheetIds)
    456             result = result.concat(frameIdToStyleSheetIds[frameId]);
    457         return result;
    458     },
    459 
    460     /**
    461      * @param {string} url
    462      * @return {Object.<NetworkAgent.FrameId, Array.<CSSAgent.StyleSheetId>>}
    463      */
    464     styleSheetIdsByFrameIdForURL: function(url)
    465     {
    466         var styleSheetIdsForFrame = this._styleSheetIdsForURL[url];
    467         if (!styleSheetIdsForFrame)
    468             return {};
    469         return styleSheetIdsForFrame;
    470     },
    471 
    472     /**
    473      * @param {CSSAgent.NamedFlow} namedFlowPayload
    474      */
    475     _namedFlowCreated: function(namedFlowPayload)
    476     {
    477         var namedFlow = WebInspector.NamedFlow.parsePayload(namedFlowPayload);
    478         var namedFlowCollection = this._namedFlowCollections[namedFlow.documentNodeId];
    479 
    480         if (!namedFlowCollection)
    481             return;
    482 
    483         namedFlowCollection._appendNamedFlow(namedFlow);
    484         this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.NamedFlowCreated, namedFlow);
    485     },
    486 
    487     /**
    488      * @param {DOMAgent.NodeId} documentNodeId
    489      * @param {string} flowName
    490      */
    491     _namedFlowRemoved: function(documentNodeId, flowName)
    492     {
    493         var namedFlowCollection = this._namedFlowCollections[documentNodeId];
    494 
    495         if (!namedFlowCollection)
    496             return;
    497 
    498         namedFlowCollection._removeNamedFlow(flowName);
    499         this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.NamedFlowRemoved, { documentNodeId: documentNodeId, flowName: flowName });
    500     },
    501 
    502     /**
    503      * @param {CSSAgent.NamedFlow} namedFlowPayload
    504      */
    505     _regionLayoutUpdated: function(namedFlowPayload)
    506     {
    507         var namedFlow = WebInspector.NamedFlow.parsePayload(namedFlowPayload);
    508         var namedFlowCollection = this._namedFlowCollections[namedFlow.documentNodeId];
    509 
    510         if (!namedFlowCollection)
    511             return;
    512 
    513         namedFlowCollection._appendNamedFlow(namedFlow);
    514         this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.RegionLayoutUpdated, namedFlow);
    515     },
    516 
    517     /**
    518      * @param {CSSAgent.NamedFlow} namedFlowPayload
    519      */
    520     _regionOversetChanged: function(namedFlowPayload)
    521     {
    522         var namedFlow = WebInspector.NamedFlow.parsePayload(namedFlowPayload);
    523         var namedFlowCollection = this._namedFlowCollections[namedFlow.documentNodeId];
    524 
    525          if (!namedFlowCollection)
    526             return;
    527 
    528         namedFlowCollection._appendNamedFlow(namedFlow);
    529         this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.RegionOversetChanged, namedFlow);
    530     },
    531 
    532     /**
    533      * @param {CSSAgent.StyleSheetId} styleSheetId
    534      * @param {string} newText
    535      * @param {boolean} majorChange
    536      * @param {function(?string)} userCallback
    537      */
    538     setStyleSheetText: function(styleSheetId, newText, majorChange, userCallback)
    539     {
    540         function callback(error)
    541         {
    542             this._pendingCommandsMajorState.pop();
    543             if (!error && majorChange)
    544                 WebInspector.domAgent.markUndoableState();
    545 
    546             if (!error && userCallback)
    547                 userCallback(error);
    548         }
    549         this._pendingCommandsMajorState.push(majorChange);
    550         CSSAgent.setStyleSheetText(styleSheetId, newText, callback.bind(this));
    551     },
    552 
    553     _undoRedoRequested: function()
    554     {
    555         this._pendingCommandsMajorState.push(true);
    556     },
    557 
    558     _undoRedoCompleted: function()
    559     {
    560         this._pendingCommandsMajorState.pop();
    561     },
    562 
    563     _mainFrameCreatedOrNavigated: function()
    564     {
    565         this._resetStyleSheets();
    566     },
    567 
    568     _resetStyleSheets: function()
    569     {
    570         /** @type {!Object.<string, !Object.<NetworkAgent.FrameId, !Array.<!CSSAgent.StyleSheetId>>>} */
    571         this._styleSheetIdsForURL = {};
    572         /** @type {!Object.<CSSAgent.StyleSheetId, !WebInspector.CSSStyleSheetHeader>} */
    573         this._styleSheetIdToHeader = {};
    574     },
    575 
    576     _resetNamedFlowCollections: function()
    577     {
    578         this._namedFlowCollections = {};
    579     },
    580 
    581     updateLocations: function()
    582     {
    583         var headers = Object.values(this._styleSheetIdToHeader);
    584         for (var i = 0; i < headers.length; ++i)
    585             headers[i].updateLocations();
    586     },
    587 
    588     /**
    589      * @param {CSSAgent.StyleSheetId} styleSheetId
    590      * @param {WebInspector.CSSLocation} rawLocation
    591      * @param {function(WebInspector.UILocation):(boolean|undefined)} updateDelegate
    592      * @return {?WebInspector.LiveLocation}
    593      */
    594     createLiveLocation: function(styleSheetId, rawLocation, updateDelegate)
    595     {
    596         if (!rawLocation)
    597             return null;
    598         var header = this.styleSheetHeaderForId(styleSheetId);
    599         if (!header)
    600             return null;
    601         return header.createLiveLocation(rawLocation, updateDelegate);
    602     },
    603 
    604     /**
    605      * @param {WebInspector.CSSLocation} rawLocation
    606      * @return {?WebInspector.UILocation}
    607      */
    608     rawLocationToUILocation: function(rawLocation)
    609     {
    610         var frameIdToSheetIds = this._styleSheetIdsForURL[rawLocation.url];
    611         if (!frameIdToSheetIds)
    612             return null;
    613         var styleSheetIds = [];
    614         for (var frameId in frameIdToSheetIds)
    615             styleSheetIds = styleSheetIds.concat(frameIdToSheetIds[frameId]);
    616         var uiLocation;
    617         for (var i = 0; !uiLocation && i < styleSheetIds.length; ++i) {
    618             var header = this.styleSheetHeaderForId(styleSheetIds[i]);
    619             console.assert(header);
    620             uiLocation = header.rawLocationToUILocation(rawLocation.lineNumber, rawLocation.columnNumber);
    621         }
    622         return uiLocation || null;
    623     },
    624 
    625     __proto__: WebInspector.Object.prototype
    626 }
    627 
    628 /**
    629  * @constructor
    630  * @extends {WebInspector.LiveLocation}
    631  * @param {WebInspector.CSSLocation} rawLocation
    632  * @param {function(WebInspector.UILocation):(boolean|undefined)} updateDelegate
    633  */
    634 WebInspector.CSSStyleModel.LiveLocation = function(rawLocation, updateDelegate, header)
    635 {
    636     WebInspector.LiveLocation.call(this, rawLocation, updateDelegate);
    637     this._header = header;
    638 }
    639 
    640 WebInspector.CSSStyleModel.LiveLocation.prototype = {
    641     /**
    642      * @return {WebInspector.UILocation}
    643      */
    644     uiLocation: function()
    645     {
    646         var cssLocation = /** @type WebInspector.CSSLocation */ (this.rawLocation());
    647         return this._header.rawLocationToUILocation(cssLocation.lineNumber, cssLocation.columnNumber);
    648     },
    649 
    650     dispose: function()
    651     {
    652         WebInspector.LiveLocation.prototype.dispose.call(this);
    653         this._header._removeLocation(this);
    654     },
    655 
    656     __proto__: WebInspector.LiveLocation.prototype
    657 }
    658 
    659 /**
    660  * @constructor
    661  * @implements {WebInspector.RawLocation}
    662  * @param {string} url
    663  * @param {number} lineNumber
    664  * @param {number=} columnNumber
    665  */
    666 WebInspector.CSSLocation = function(url, lineNumber, columnNumber)
    667 {
    668     this.url = url;
    669     this.lineNumber = lineNumber;
    670     this.columnNumber = columnNumber || 0;
    671 }
    672 
    673 /**
    674  * @constructor
    675  * @param {CSSAgent.CSSStyle} payload
    676  */
    677 WebInspector.CSSStyleDeclaration = function(payload)
    678 {
    679     this.id = payload.styleId;
    680     this.width = payload.width;
    681     this.height = payload.height;
    682     this.range = payload.range;
    683     this._shorthandValues = WebInspector.CSSStyleDeclaration.buildShorthandValueMap(payload.shorthandEntries);
    684     this._livePropertyMap = {}; // LIVE properties (source-based or style-based) : { name -> CSSProperty }
    685     this._allProperties = []; // ALL properties: [ CSSProperty ]
    686     this.__disabledProperties = {}; // DISABLED properties: { index -> CSSProperty }
    687     var payloadPropertyCount = payload.cssProperties.length;
    688 
    689     var propertyIndex = 0;
    690     for (var i = 0; i < payloadPropertyCount; ++i) {
    691         var property = WebInspector.CSSProperty.parsePayload(this, i, payload.cssProperties[i]);
    692         this._allProperties.push(property);
    693         if (property.disabled)
    694             this.__disabledProperties[i] = property;
    695         if (!property.active && !property.styleBased)
    696             continue;
    697         var name = property.name;
    698         this[propertyIndex] = name;
    699         this._livePropertyMap[name] = property;
    700         ++propertyIndex;
    701     }
    702     this.length = propertyIndex;
    703     if ("cssText" in payload)
    704         this.cssText = payload.cssText;
    705 }
    706 
    707 /**
    708  * @param {Array.<CSSAgent.ShorthandEntry>} shorthandEntries
    709  * @return {Object}
    710  */
    711 WebInspector.CSSStyleDeclaration.buildShorthandValueMap = function(shorthandEntries)
    712 {
    713     var result = {};
    714     for (var i = 0; i < shorthandEntries.length; ++i)
    715         result[shorthandEntries[i].name] = shorthandEntries[i].value;
    716     return result;
    717 }
    718 
    719 /**
    720  * @param {CSSAgent.CSSStyle} payload
    721  * @return {WebInspector.CSSStyleDeclaration}
    722  */
    723 WebInspector.CSSStyleDeclaration.parsePayload = function(payload)
    724 {
    725     return new WebInspector.CSSStyleDeclaration(payload);
    726 }
    727 
    728 /**
    729  * @param {Array.<CSSAgent.CSSComputedStyleProperty>} payload
    730  * @return {WebInspector.CSSStyleDeclaration}
    731  */
    732 WebInspector.CSSStyleDeclaration.parseComputedStylePayload = function(payload)
    733 {
    734     var newPayload = /** @type {CSSAgent.CSSStyle} */ ({ cssProperties: [], shorthandEntries: [], width: "", height: "" });
    735     if (payload)
    736         newPayload.cssProperties = payload;
    737 
    738     return new WebInspector.CSSStyleDeclaration(newPayload);
    739 }
    740 
    741 WebInspector.CSSStyleDeclaration.prototype = {
    742     get allProperties()
    743     {
    744         return this._allProperties;
    745     },
    746 
    747     /**
    748      * @param {string} name
    749      * @return {WebInspector.CSSProperty|undefined}
    750      */
    751     getLiveProperty: function(name)
    752     {
    753         return this._livePropertyMap[name];
    754     },
    755 
    756     /**
    757      * @param {string} name
    758      * @return {string}
    759      */
    760     getPropertyValue: function(name)
    761     {
    762         var property = this._livePropertyMap[name];
    763         return property ? property.value : "";
    764     },
    765 
    766     /**
    767      * @param {string} name
    768      * @return {string}
    769      */
    770     getPropertyPriority: function(name)
    771     {
    772         var property = this._livePropertyMap[name];
    773         return property ? property.priority : "";
    774     },
    775 
    776     /**
    777      * @param {string} name
    778      * @return {boolean}
    779      */
    780     isPropertyImplicit: function(name)
    781     {
    782         var property = this._livePropertyMap[name];
    783         return property ? property.implicit : "";
    784     },
    785 
    786     /**
    787      * @param {string} name
    788      * @return {Array.<WebInspector.CSSProperty>}
    789      */
    790     longhandProperties: function(name)
    791     {
    792         var longhands = WebInspector.CSSMetadata.cssPropertiesMetainfo.longhands(name);
    793         var result = [];
    794         for (var i = 0; longhands && i < longhands.length; ++i) {
    795             var property = this._livePropertyMap[longhands[i]];
    796             if (property)
    797                 result.push(property);
    798         }
    799         return result;
    800     },
    801 
    802     /**
    803      * @param {string} shorthandProperty
    804      * @return {string}
    805      */
    806     shorthandValue: function(shorthandProperty)
    807     {
    808         return this._shorthandValues[shorthandProperty];
    809     },
    810 
    811     /**
    812      * @param {number} index
    813      * @return {?WebInspector.CSSProperty}
    814      */
    815     propertyAt: function(index)
    816     {
    817         return (index < this.allProperties.length) ? this.allProperties[index] : null;
    818     },
    819 
    820     /**
    821      * @return {number}
    822      */
    823     pastLastSourcePropertyIndex: function()
    824     {
    825         for (var i = this.allProperties.length - 1; i >= 0; --i) {
    826             var property = this.allProperties[i];
    827             if (property.active || property.disabled)
    828                 return i + 1;
    829         }
    830         return 0;
    831     },
    832 
    833     /**
    834      * @param {number=} index
    835      */
    836     newBlankProperty: function(index)
    837     {
    838         index = (typeof index === "undefined") ? this.pastLastSourcePropertyIndex() : index;
    839         return new WebInspector.CSSProperty(this, index, "", "", "", "active", true, false, "");
    840     },
    841 
    842     /**
    843      * @param {number} index
    844      * @param {string} name
    845      * @param {string} value
    846      * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback
    847      */
    848     insertPropertyAt: function(index, name, value, userCallback)
    849     {
    850         /**
    851          * @param {?string} error
    852          * @param {CSSAgent.CSSStyle} payload
    853          */
    854         function callback(error, payload)
    855         {
    856             WebInspector.cssModel._pendingCommandsMajorState.pop();
    857             if (!userCallback)
    858                 return;
    859 
    860             if (error) {
    861                 console.error(error);
    862                 userCallback(null);
    863             } else
    864                 userCallback(WebInspector.CSSStyleDeclaration.parsePayload(payload));
    865         }
    866 
    867         if (!this.id)
    868             throw "No style id";
    869 
    870         WebInspector.cssModel._pendingCommandsMajorState.push(true);
    871         CSSAgent.setPropertyText(this.id, index, name + ": " + value + ";", false, callback.bind(this));
    872     },
    873 
    874     /**
    875      * @param {string} name
    876      * @param {string} value
    877      * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback
    878      */
    879     appendProperty: function(name, value, userCallback)
    880     {
    881         this.insertPropertyAt(this.allProperties.length, name, value, userCallback);
    882     },
    883 
    884     /**
    885      * @param {string} text
    886      * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback
    887      */
    888     setText: function(text, userCallback)
    889     {
    890         /**
    891          * @param {?string} error
    892          * @param {CSSAgent.CSSStyle} payload
    893          */
    894         function callback(error, payload)
    895         {
    896             WebInspector.cssModel._pendingCommandsMajorState.pop();
    897             if (!userCallback)
    898                 return;
    899 
    900             if (error) {
    901                 console.error(error);
    902                 userCallback(null);
    903             } else
    904                 userCallback(WebInspector.CSSStyleDeclaration.parsePayload(payload));
    905         }
    906 
    907         if (!this.id)
    908             throw "No style id";
    909 
    910         if (typeof this.cssText === "undefined") {
    911             userCallback(null);
    912             return;
    913         }
    914 
    915         WebInspector.cssModel._pendingCommandsMajorState.push(true);
    916         CSSAgent.setStyleText(this.id, text, callback);
    917     }
    918 }
    919 
    920 /**
    921  * @constructor
    922  * @param {CSSAgent.CSSRule} payload
    923  * @param {Array.<number>=} matchingSelectors
    924  */
    925 WebInspector.CSSRule = function(payload, matchingSelectors)
    926 {
    927     this.id = payload.ruleId;
    928     if (matchingSelectors)
    929         this.matchingSelectors = matchingSelectors;
    930     this.selectors = payload.selectorList.selectors;
    931     this.selectorText = this.selectors.join(", ");
    932     this.selectorRange = payload.selectorList.range;
    933     this.sourceURL = payload.sourceURL;
    934     this.origin = payload.origin;
    935     this.style = WebInspector.CSSStyleDeclaration.parsePayload(payload.style);
    936     this.style.parentRule = this;
    937     if (payload.media)
    938         this.media = WebInspector.CSSMedia.parseMediaArrayPayload(payload.media);
    939     this._setRawLocationAndFrameId();
    940 }
    941 
    942 /**
    943  * @param {CSSAgent.CSSRule} payload
    944  * @param {Array.<number>=} matchingIndices
    945  * @return {WebInspector.CSSRule}
    946  */
    947 WebInspector.CSSRule.parsePayload = function(payload, matchingIndices)
    948 {
    949     return new WebInspector.CSSRule(payload, matchingIndices);
    950 }
    951 
    952 WebInspector.CSSRule.prototype = {
    953     _setRawLocationAndFrameId: function()
    954     {
    955         if (!this.id)
    956             return;
    957         var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.id.styleSheetId);
    958         this.frameId = styleSheetHeader.frameId;
    959         var url = styleSheetHeader.resourceURL();
    960         if (!url)
    961             return;
    962         this.rawLocation = new WebInspector.CSSLocation(url, this.lineNumberInSource(), this.columnNumberInSource());
    963     },
    964 
    965     /**
    966      * @return {string}
    967      */
    968     resourceURL: function()
    969     {
    970         if (!this.id)
    971             return "";
    972         var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.id.styleSheetId);
    973         return styleSheetHeader.resourceURL();
    974     },
    975 
    976     /**
    977      * @return {number}
    978      */
    979     lineNumberInSource: function()
    980     {
    981         if (!this.selectorRange)
    982             return 0;
    983         var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.id.styleSheetId);
    984         return styleSheetHeader.lineNumberInSource(this.selectorRange.startLine);
    985     },
    986 
    987     /**
    988      * @return {number|undefined}
    989      */
    990     columnNumberInSource: function()
    991     {
    992         if (!this.selectorRange)
    993             return undefined;
    994         var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.id.styleSheetId);
    995         console.assert(styleSheetHeader);
    996         return styleSheetHeader.columnNumberInSource(this.selectorRange.startLine, this.selectorRange.startColumn);
    997     },
    998 
    999     get isUserAgent()
   1000     {
   1001         return this.origin === "user-agent";
   1002     },
   1003 
   1004     get isUser()
   1005     {
   1006         return this.origin === "user";
   1007     },
   1008 
   1009     get isViaInspector()
   1010     {
   1011         return this.origin === "inspector";
   1012     },
   1013 
   1014     get isRegular()
   1015     {
   1016         return this.origin === "regular";
   1017     }
   1018 }
   1019 
   1020 /**
   1021  * @constructor
   1022  * @param {?WebInspector.CSSStyleDeclaration} ownerStyle
   1023  * @param {number} index
   1024  * @param {string} name
   1025  * @param {string} value
   1026  * @param {?string} priority
   1027  * @param {string} status
   1028  * @param {boolean} parsedOk
   1029  * @param {boolean} implicit
   1030  * @param {?string=} text
   1031  * @param {CSSAgent.SourceRange=} range
   1032  */
   1033 WebInspector.CSSProperty = function(ownerStyle, index, name, value, priority, status, parsedOk, implicit, text, range)
   1034 {
   1035     this.ownerStyle = ownerStyle;
   1036     this.index = index;
   1037     this.name = name;
   1038     this.value = value;
   1039     this.priority = priority;
   1040     this.status = status;
   1041     this.parsedOk = parsedOk;
   1042     this.implicit = implicit;
   1043     this.text = text;
   1044     this.range = range;
   1045 }
   1046 
   1047 /**
   1048  * @param {?WebInspector.CSSStyleDeclaration} ownerStyle
   1049  * @param {number} index
   1050  * @param {CSSAgent.CSSProperty} payload
   1051  * @return {WebInspector.CSSProperty}
   1052  */
   1053 WebInspector.CSSProperty.parsePayload = function(ownerStyle, index, payload)
   1054 {
   1055     // The following default field values are used in the payload:
   1056     // priority: ""
   1057     // parsedOk: true
   1058     // implicit: false
   1059     // status: "style"
   1060     var result = new WebInspector.CSSProperty(
   1061         ownerStyle, index, payload.name, payload.value, payload.priority || "", payload.status || "style", ("parsedOk" in payload) ? !!payload.parsedOk : true, !!payload.implicit, payload.text, payload.range);
   1062     return result;
   1063 }
   1064 
   1065 WebInspector.CSSProperty.prototype = {
   1066     get propertyText()
   1067     {
   1068         if (this.text !== undefined)
   1069             return this.text;
   1070 
   1071         if (this.name === "")
   1072             return "";
   1073         return this.name + ": " + this.value + (this.priority ? " !" + this.priority : "") + ";";
   1074     },
   1075 
   1076     get isLive()
   1077     {
   1078         return this.active || this.styleBased;
   1079     },
   1080 
   1081     get active()
   1082     {
   1083         return this.status === "active";
   1084     },
   1085 
   1086     get styleBased()
   1087     {
   1088         return this.status === "style";
   1089     },
   1090 
   1091     get inactive()
   1092     {
   1093         return this.status === "inactive";
   1094     },
   1095 
   1096     get disabled()
   1097     {
   1098         return this.status === "disabled";
   1099     },
   1100 
   1101     /**
   1102      * Replaces "propertyName: propertyValue [!important];" in the stylesheet by an arbitrary propertyText.
   1103      *
   1104      * @param {string} propertyText
   1105      * @param {boolean} majorChange
   1106      * @param {boolean} overwrite
   1107      * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback
   1108      */
   1109     setText: function(propertyText, majorChange, overwrite, userCallback)
   1110     {
   1111         /**
   1112          * @param {?WebInspector.CSSStyleDeclaration} style
   1113          */
   1114         function enabledCallback(style)
   1115         {
   1116             if (userCallback)
   1117                 userCallback(style);
   1118         }
   1119 
   1120         /**
   1121          * @param {?string} error
   1122          * @param {?CSSAgent.CSSStyle} stylePayload
   1123          */
   1124         function callback(error, stylePayload)
   1125         {
   1126             WebInspector.cssModel._pendingCommandsMajorState.pop();
   1127             if (!error) {
   1128                 if (majorChange)
   1129                     WebInspector.domAgent.markUndoableState();
   1130                 this.text = propertyText;
   1131                 var style = WebInspector.CSSStyleDeclaration.parsePayload(stylePayload);
   1132                 var newProperty = style.allProperties[this.index];
   1133 
   1134                 if (newProperty && this.disabled && !propertyText.match(/^\s*$/)) {
   1135                     newProperty.setDisabled(false, enabledCallback);
   1136                     return;
   1137                 }
   1138 
   1139                 if (userCallback)
   1140                     userCallback(style);
   1141             } else {
   1142                 if (userCallback)
   1143                     userCallback(null);
   1144             }
   1145         }
   1146 
   1147         if (!this.ownerStyle)
   1148             throw "No ownerStyle for property";
   1149 
   1150         if (!this.ownerStyle.id)
   1151             throw "No owner style id";
   1152 
   1153         // An index past all the properties adds a new property to the style.
   1154         WebInspector.cssModel._pendingCommandsMajorState.push(majorChange);
   1155         CSSAgent.setPropertyText(this.ownerStyle.id, this.index, propertyText, overwrite, callback.bind(this));
   1156     },
   1157 
   1158     /**
   1159      * @param {string} newValue
   1160      * @param {boolean} majorChange
   1161      * @param {boolean} overwrite
   1162      * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback
   1163      */
   1164     setValue: function(newValue, majorChange, overwrite, userCallback)
   1165     {
   1166         var text = this.name + ": " + newValue + (this.priority ? " !" + this.priority : "") + ";"
   1167         this.setText(text, majorChange, overwrite, userCallback);
   1168     },
   1169 
   1170     /**
   1171      * @param {boolean} disabled
   1172      * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback
   1173      */
   1174     setDisabled: function(disabled, userCallback)
   1175     {
   1176         if (!this.ownerStyle && userCallback)
   1177             userCallback(null);
   1178         if (disabled === this.disabled && userCallback)
   1179             userCallback(this.ownerStyle);
   1180 
   1181         /**
   1182          * @param {?string} error
   1183          * @param {CSSAgent.CSSStyle} stylePayload
   1184          */
   1185         function callback(error, stylePayload)
   1186         {
   1187             WebInspector.cssModel._pendingCommandsMajorState.pop();
   1188             if (error) {
   1189                 if (userCallback)
   1190                     userCallback(null);
   1191                 return;
   1192             }
   1193             WebInspector.domAgent.markUndoableState();
   1194             if (userCallback) {
   1195                 var style = WebInspector.CSSStyleDeclaration.parsePayload(stylePayload);
   1196                 userCallback(style);
   1197             }
   1198         }
   1199 
   1200         if (!this.ownerStyle.id)
   1201             throw "No owner style id";
   1202 
   1203         WebInspector.cssModel._pendingCommandsMajorState.push(false);
   1204         CSSAgent.toggleProperty(this.ownerStyle.id, this.index, disabled, callback.bind(this));
   1205     },
   1206 
   1207     /**
   1208      * @param {boolean} forName
   1209      * @return {WebInspector.UILocation}
   1210      */
   1211     uiLocation: function(forName)
   1212     {
   1213         if (!this.range || !this.ownerStyle || !this.ownerStyle.parentRule)
   1214             return null;
   1215 
   1216         var url = this.ownerStyle.parentRule.resourceURL();
   1217         if (!url)
   1218             return null;
   1219 
   1220         var range = this.range;
   1221         var line = forName ? range.startLine : range.endLine;
   1222         // End of range is exclusive, so subtract 1 from the end offset.
   1223         var column = forName ? range.startColumn : range.endColumn - (this.text && this.text.endsWith(";") ? 2 : 1);
   1224         var rawLocation = new WebInspector.CSSLocation(url, line, column);
   1225         return WebInspector.cssModel.rawLocationToUILocation(rawLocation);
   1226     }
   1227 }
   1228 
   1229 /**
   1230  * @constructor
   1231  * @param {CSSAgent.CSSMedia} payload
   1232  */
   1233 WebInspector.CSSMedia = function(payload)
   1234 {
   1235     this.text = payload.text;
   1236     this.source = payload.source;
   1237     this.sourceURL = payload.sourceURL || "";
   1238     this.range = payload.range;
   1239     this.parentStyleSheetId = payload.parentStyleSheetId;
   1240 }
   1241 
   1242 WebInspector.CSSMedia.Source = {
   1243     LINKED_SHEET: "linkedSheet",
   1244     INLINE_SHEET: "inlineSheet",
   1245     MEDIA_RULE: "mediaRule",
   1246     IMPORT_RULE: "importRule"
   1247 };
   1248 
   1249 /**
   1250  * @param {CSSAgent.CSSMedia} payload
   1251  * @return {WebInspector.CSSMedia}
   1252  */
   1253 WebInspector.CSSMedia.parsePayload = function(payload)
   1254 {
   1255     return new WebInspector.CSSMedia(payload);
   1256 }
   1257 
   1258 /**
   1259  * @param {Array.<CSSAgent.CSSMedia>} payload
   1260  * @return {Array.<WebInspector.CSSMedia>}
   1261  */
   1262 WebInspector.CSSMedia.parseMediaArrayPayload = function(payload)
   1263 {
   1264     var result = [];
   1265     for (var i = 0; i < payload.length; ++i)
   1266         result.push(WebInspector.CSSMedia.parsePayload(payload[i]));
   1267     return result;
   1268 }
   1269 
   1270 WebInspector.CSSMedia.prototype = {
   1271     /**
   1272      * @return {number|undefined}
   1273      */
   1274     lineNumberInSource: function()
   1275     {
   1276         if (!this.range)
   1277             return undefined;
   1278         var header = this.header();
   1279         if (!header)
   1280             return undefined;
   1281         return header.lineNumberInSource(this.range.startLine);
   1282     },
   1283 
   1284     /**
   1285      * @return {number|undefined}
   1286      */
   1287     columnNumberInSource: function()
   1288     {
   1289         if (!this.range)
   1290             return undefined;
   1291         var header = this.header();
   1292         if (!header)
   1293             return undefined;
   1294         return header.columnNumberInSource(this.range.startLine, this.range.startColumn);
   1295     },
   1296 
   1297     /**
   1298      * @return {?WebInspector.CSSStyleSheetHeader}
   1299      */
   1300     header: function()
   1301     {
   1302         return this.parentStyleSheetId ? WebInspector.cssModel.styleSheetHeaderForId(this.parentStyleSheetId) : null;
   1303     }
   1304 }
   1305 
   1306 /**
   1307  * @constructor
   1308  * @implements {WebInspector.ContentProvider}
   1309  * @param {CSSAgent.CSSStyleSheetHeader} payload
   1310  */
   1311 WebInspector.CSSStyleSheetHeader = function(payload)
   1312 {
   1313     this.id = payload.styleSheetId;
   1314     this.frameId = payload.frameId;
   1315     this.sourceURL = payload.sourceURL;
   1316     this.hasSourceURL = !!payload.hasSourceURL;
   1317     this.sourceMapURL = payload.sourceMapURL;
   1318     this.origin = payload.origin;
   1319     this.title = payload.title;
   1320     this.disabled = payload.disabled;
   1321     this.isInline = payload.isInline;
   1322     this.startLine = payload.startLine;
   1323     this.startColumn = payload.startColumn;
   1324     /** @type {!Set.<!WebInspector.CSSStyleModel.LiveLocation>} */
   1325     this._locations = new Set();
   1326     /** @type {!Array.<!WebInspector.SourceMapping>} */
   1327     this._sourceMappings = [];
   1328 }
   1329 
   1330 WebInspector.CSSStyleSheetHeader.prototype = {
   1331     /**
   1332      * @return {string}
   1333      */
   1334     resourceURL: function()
   1335     {
   1336         return this.origin === "inspector" ? this._viaInspectorResourceURL() : this.sourceURL;
   1337     },
   1338 
   1339     /**
   1340      * @param {WebInspector.CSSLocation} rawLocation
   1341      * @param {function(WebInspector.UILocation):(boolean|undefined)} updateDelegate
   1342      * @return {?WebInspector.LiveLocation}
   1343      */
   1344     createLiveLocation: function(rawLocation, updateDelegate)
   1345     {
   1346         var location = new WebInspector.CSSStyleModel.LiveLocation(rawLocation, updateDelegate, this);
   1347         this._locations.add(location);
   1348         location.update();
   1349         return location;
   1350     },
   1351 
   1352     updateLocations: function()
   1353     {
   1354         var items = this._locations.items();
   1355         for (var i = 0; i < items.length; ++i)
   1356             items[i].update();
   1357     },
   1358 
   1359     /**
   1360      * @param {!WebInspector.CSSStyleModel.LiveLocation} location
   1361      */
   1362     _removeLocation: function(location)
   1363     {
   1364         this._locations.remove(location);
   1365     },
   1366 
   1367     /**
   1368      * @param {number} lineNumber
   1369      * @param {number=} columnNumber
   1370      * @return {?WebInspector.UILocation}
   1371      */
   1372     rawLocationToUILocation: function(lineNumber, columnNumber)
   1373     {
   1374         var uiLocation;
   1375         var rawLocation = new WebInspector.CSSLocation(this.resourceURL(), lineNumber, columnNumber || 0);
   1376         for (var i = this._sourceMappings.length - 1; !uiLocation && i >= 0; --i)
   1377             uiLocation = this._sourceMappings[i].rawLocationToUILocation(rawLocation);
   1378         return uiLocation || null;
   1379     },
   1380 
   1381     /**
   1382      * @param {!WebInspector.SourceMapping} sourceMapping
   1383      */
   1384     pushSourceMapping: function(sourceMapping)
   1385     {
   1386         this._sourceMappings.push(sourceMapping);
   1387         this.updateLocations();
   1388     },
   1389 
   1390     /**
   1391      * @return {string}
   1392      */
   1393     _key: function()
   1394     {
   1395         return this.frameId + ":" + this.resourceURL();
   1396     },
   1397 
   1398     /**
   1399      * @return {string}
   1400      */
   1401     _viaInspectorResourceURL: function()
   1402     {
   1403         var frame = WebInspector.resourceTreeModel.frameForId(this.frameId);
   1404         console.assert(frame);
   1405         var parsedURL = new WebInspector.ParsedURL(frame.url);
   1406         var fakeURL = "inspector://" + parsedURL.host + parsedURL.folderPathComponents;
   1407         if (!fakeURL.endsWith("/"))
   1408             fakeURL += "/";
   1409         fakeURL += "inspector-stylesheet";
   1410         return fakeURL;
   1411     },
   1412 
   1413     /**
   1414      * @param {number} lineNumberInStyleSheet
   1415      * @return {number}
   1416      */
   1417     lineNumberInSource: function(lineNumberInStyleSheet)
   1418     {
   1419         return this.startLine + lineNumberInStyleSheet;
   1420     },
   1421 
   1422     /**
   1423      * @param {number} lineNumberInStyleSheet
   1424      * @param {number} columnNumberInStyleSheet
   1425      * @return {number|undefined}
   1426      */
   1427     columnNumberInSource: function(lineNumberInStyleSheet, columnNumberInStyleSheet)
   1428     {
   1429         return (lineNumberInStyleSheet ? 0 : this.startColumn) + columnNumberInStyleSheet;
   1430     },
   1431 
   1432     /**
   1433      * @override
   1434      */
   1435     contentURL: function()
   1436     {
   1437         return this.resourceURL();
   1438     },
   1439 
   1440     /**
   1441      * @override
   1442      */
   1443     contentType: function()
   1444     {
   1445         return WebInspector.resourceTypes.Stylesheet;
   1446     },
   1447 
   1448     /**
   1449      * @override
   1450      */
   1451     requestContent: function(callback)
   1452     {
   1453         CSSAgent.getStyleSheetText(this.id, textCallback.bind(this));
   1454 
   1455         function textCallback(error, text)
   1456         {
   1457             if (error) {
   1458                 WebInspector.log("Failed to get text for stylesheet " + this.id + ": " + error);
   1459                 text = "";
   1460                 // Fall through.
   1461             }
   1462             callback(text, false, "text/css");
   1463         }
   1464     },
   1465 
   1466     /**
   1467      * @override
   1468      */
   1469     searchInContent: function(query, caseSensitive, isRegex, callback)
   1470     {
   1471         function performSearch(content)
   1472         {
   1473             callback(WebInspector.ContentProvider.performSearchInContent(content, query, caseSensitive, isRegex));
   1474         }
   1475 
   1476         // searchInContent should call back later.
   1477         this.requestContent(performSearch);
   1478     }
   1479 }
   1480 
   1481 /**
   1482  * @constructor
   1483  * @param {CSSAgent.CSSStyleSheetBody} payload
   1484  */
   1485 WebInspector.CSSStyleSheet = function(payload)
   1486 {
   1487     this.id = payload.styleSheetId;
   1488     this.rules = [];
   1489     this.styles = {};
   1490     for (var i = 0; i < payload.rules.length; ++i) {
   1491         var rule = WebInspector.CSSRule.parsePayload(payload.rules[i]);
   1492         this.rules.push(rule);
   1493         if (rule.style)
   1494             this.styles[rule.style.id] = rule.style;
   1495     }
   1496     if ("text" in payload)
   1497         this._text = payload.text;
   1498 }
   1499 
   1500 /**
   1501  * @param {CSSAgent.StyleSheetId} styleSheetId
   1502  * @param {function(?WebInspector.CSSStyleSheet)} userCallback
   1503  */
   1504 WebInspector.CSSStyleSheet.createForId = function(styleSheetId, userCallback)
   1505 {
   1506     /**
   1507      * @param {?string} error
   1508      * @param {CSSAgent.CSSStyleSheetBody} styleSheetPayload
   1509      */
   1510     function callback(error, styleSheetPayload)
   1511     {
   1512         if (error)
   1513             userCallback(null);
   1514         else
   1515             userCallback(new WebInspector.CSSStyleSheet(styleSheetPayload));
   1516     }
   1517     CSSAgent.getStyleSheet(styleSheetId, callback.bind(this));
   1518 }
   1519 
   1520 WebInspector.CSSStyleSheet.prototype = {
   1521     /**
   1522      * @return {string|undefined}
   1523      */
   1524     getText: function()
   1525     {
   1526         return this._text;
   1527     },
   1528 
   1529     /**
   1530      * @param {string} newText
   1531      * @param {boolean} majorChange
   1532      * @param {function(?string)=} userCallback
   1533      */
   1534     setText: function(newText, majorChange, userCallback)
   1535     {
   1536         /**
   1537          * @param {?string} error
   1538          */
   1539         function callback(error)
   1540         {
   1541             if (!error)
   1542                 WebInspector.domAgent.markUndoableState();
   1543 
   1544             WebInspector.cssModel._pendingCommandsMajorState.pop();
   1545             if (userCallback)
   1546                 userCallback(error);
   1547         }
   1548 
   1549         WebInspector.cssModel._pendingCommandsMajorState.push(majorChange);
   1550         CSSAgent.setStyleSheetText(this.id, newText, callback.bind(this));
   1551     }
   1552 }
   1553 
   1554 /**
   1555  * @constructor
   1556  * @implements {CSSAgent.Dispatcher}
   1557  * @param {WebInspector.CSSStyleModel} cssModel
   1558  */
   1559 WebInspector.CSSDispatcher = function(cssModel)
   1560 {
   1561     this._cssModel = cssModel;
   1562 }
   1563 
   1564 WebInspector.CSSDispatcher.prototype = {
   1565     mediaQueryResultChanged: function()
   1566     {
   1567         this._cssModel.mediaQueryResultChanged();
   1568     },
   1569 
   1570     /**
   1571      * @param {CSSAgent.StyleSheetId} styleSheetId
   1572      */
   1573     styleSheetChanged: function(styleSheetId)
   1574     {
   1575         this._cssModel._fireStyleSheetChanged(styleSheetId);
   1576     },
   1577 
   1578     /**
   1579      * @param {CSSAgent.CSSStyleSheetHeader} header
   1580      */
   1581     styleSheetAdded: function(header)
   1582     {
   1583         this._cssModel._styleSheetAdded(header);
   1584     },
   1585 
   1586     /**
   1587      * @param {CSSAgent.StyleSheetId} id
   1588      */
   1589     styleSheetRemoved: function(id)
   1590     {
   1591         this._cssModel._styleSheetRemoved(id);
   1592     },
   1593 
   1594     /**
   1595      * @param {CSSAgent.NamedFlow} namedFlowPayload
   1596      */
   1597     namedFlowCreated: function(namedFlowPayload)
   1598     {
   1599         this._cssModel._namedFlowCreated(namedFlowPayload);
   1600     },
   1601 
   1602     /**
   1603      * @param {DOMAgent.NodeId} documentNodeId
   1604      * @param {string} flowName
   1605      */
   1606     namedFlowRemoved: function(documentNodeId, flowName)
   1607     {
   1608         this._cssModel._namedFlowRemoved(documentNodeId, flowName);
   1609     },
   1610 
   1611     /**
   1612      * @param {CSSAgent.NamedFlow} namedFlowPayload
   1613      */
   1614     regionLayoutUpdated: function(namedFlowPayload)
   1615     {
   1616         this._cssModel._regionLayoutUpdated(namedFlowPayload);
   1617     },
   1618 
   1619     /**
   1620      * @param {CSSAgent.NamedFlow} namedFlowPayload
   1621      */
   1622     regionOversetChanged: function(namedFlowPayload)
   1623     {
   1624         this._cssModel._regionOversetChanged(namedFlowPayload);
   1625     }
   1626 }
   1627 
   1628 /**
   1629  * @constructor
   1630  * @param {CSSAgent.NamedFlow} payload
   1631  */
   1632 WebInspector.NamedFlow = function(payload)
   1633 {
   1634     this.documentNodeId = payload.documentNodeId;
   1635     this.name = payload.name;
   1636     this.overset = payload.overset;
   1637     this.content = payload.content;
   1638     this.regions = payload.regions;
   1639 }
   1640 
   1641 /**
   1642  * @param {CSSAgent.NamedFlow} payload
   1643  * @return {WebInspector.NamedFlow}
   1644  */
   1645 WebInspector.NamedFlow.parsePayload = function(payload)
   1646 {
   1647     return new WebInspector.NamedFlow(payload);
   1648 }
   1649 
   1650 /**
   1651  * @constructor
   1652  * @param {Array.<CSSAgent.NamedFlow>} payload
   1653  */
   1654 WebInspector.NamedFlowCollection = function(payload)
   1655 {
   1656     /** @type {Object.<string, WebInspector.NamedFlow>} */
   1657     this.namedFlowMap = {};
   1658 
   1659     for (var i = 0; i < payload.length; ++i) {
   1660         var namedFlow = WebInspector.NamedFlow.parsePayload(payload[i]);
   1661         this.namedFlowMap[namedFlow.name] = namedFlow;
   1662     }
   1663 }
   1664 
   1665 WebInspector.NamedFlowCollection.prototype = {
   1666     /**
   1667      * @param {WebInspector.NamedFlow} namedFlow
   1668      */
   1669     _appendNamedFlow: function(namedFlow)
   1670     {
   1671         this.namedFlowMap[namedFlow.name] = namedFlow;
   1672     },
   1673 
   1674     /**
   1675      * @param {string} flowName
   1676      */
   1677     _removeNamedFlow: function(flowName)
   1678     {
   1679         delete this.namedFlowMap[flowName];
   1680     },
   1681 
   1682     /**
   1683      * @param {string} flowName
   1684      * @return {WebInspector.NamedFlow}
   1685      */
   1686     flowByName: function(flowName)
   1687     {
   1688         var namedFlow = this.namedFlowMap[flowName];
   1689 
   1690         if (!namedFlow)
   1691             return null;
   1692         return namedFlow;
   1693     }
   1694 }
   1695 /**
   1696  * @type {WebInspector.CSSStyleModel}
   1697  */
   1698 WebInspector.cssModel = null;
   1699