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