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