• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
36WebInspector.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
57WebInspector.CSSStyleModel.PseudoStatePropertyName = "pseudoState";
58
59/**
60 * @param {!WebInspector.CSSStyleModel} cssModel
61 * @param {!Array.<!CSSAgent.RuleMatch>|undefined} matchArray
62 * @return {!Array.<!WebInspector.CSSRule>}
63 */
64WebInspector.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
75WebInspector.CSSStyleModel.Events = {
76    ModelWasEnabled: "ModelWasEnabled",
77    StyleSheetAdded: "StyleSheetAdded",
78    StyleSheetChanged: "StyleSheetChanged",
79    StyleSheetRemoved: "StyleSheetRemoved",
80    MediaQueryResultChanged: "MediaQueryResultChanged",
81}
82
83WebInspector.CSSStyleModel.MediaTypes = ["all", "braille", "embossed", "handheld", "print", "projection", "screen", "speech", "tty", "tv"];
84
85WebInspector.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 */
611WebInspector.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
620WebInspector.CSSLocation.prototype = {
621    __proto__: WebInspector.SDKObject.prototype
622}
623
624/**
625 * @constructor
626 * @param {!WebInspector.CSSStyleModel} cssModel
627 * @param {!CSSAgent.CSSStyle} payload
628 */
629WebInspector.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 */
669WebInspector.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 */
682WebInspector.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 */
692WebInspector.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
701WebInspector.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 */
896WebInspector.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 */
907WebInspector.CSSRuleSelector.parsePayload = function(payload)
908{
909    return new WebInspector.CSSRuleSelector(payload)
910}
911
912WebInspector.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 */
931WebInspector.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 */
969WebInspector.CSSRule.parsePayload = function(cssModel, payload, matchingIndices)
970{
971    return new WebInspector.CSSRule(cssModel, payload, matchingIndices);
972}
973
974WebInspector.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 */
1086WebInspector.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 */
1106WebInspector.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
1118WebInspector.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 */
1262WebInspector.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 */
1274WebInspector.CSSMediaQuery.parsePayload = function(payload)
1275{
1276    return new WebInspector.CSSMediaQuery(payload);
1277}
1278
1279WebInspector.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 */
1301WebInspector.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 */
1313WebInspector.CSSMediaQueryExpression.parsePayload = function(payload)
1314{
1315    return new WebInspector.CSSMediaQueryExpression(payload);
1316}
1317
1318WebInspector.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 */
1358WebInspector.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
1374WebInspector.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 */
1386WebInspector.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 */
1396WebInspector.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
1404WebInspector.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 */
1470WebInspector.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
1486WebInspector.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 */
1628WebInspector.CSSDispatcher = function(cssModel)
1629{
1630    this._cssModel = cssModel;
1631}
1632
1633WebInspector.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 */
1668WebInspector.CSSStyleModel.ComputedStyleLoader = function(cssModel)
1669{
1670    this._cssModel = cssModel;
1671    /** @type {!Object.<*, !Array.<function(?WebInspector.CSSStyleDeclaration)>>} */
1672    this._nodeIdToCallbackData = {};
1673}
1674
1675WebInspector.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 */
1716WebInspector.cssModel;
1717