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