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