2db3c8fa16ae0c3aab2ef0335ffb0e05a520d7b0
[WebKit-https.git] / Source / WebCore / inspector / front-end / CSSStyleModel.js
1 /*
2  * Copyright (C) 2010 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /**
32  * @constructor
33  * @extends {WebInspector.Object}
34  * @param {WebInspector.Workspace} workspace
35  */
36 WebInspector.CSSStyleModel = function(workspace)
37 {
38     this._workspace = workspace;
39     this._pendingCommandsMajorState = [];
40     /** @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  */
56 WebInspector.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  */
67 WebInspector.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
75 WebInspector.CSSStyleModel.Events = {
76     StyleSheetChanged: "StyleSheetChanged",
77     MediaQueryResultChanged: "MediaQueryResultChanged",
78     NamedFlowCreated: "NamedFlowCreated",
79     NamedFlowRemoved: "NamedFlowRemoved",
80     RegionLayoutUpdated: "RegionLayoutUpdated"
81 }
82
83 WebInspector.CSSStyleModel.MediaTypes = ["all", "braille", "embossed", "handheld", "print", "projection", "screen", "speech", "tty", "tv"];
84
85 WebInspector.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     /**
558      * @param {DOMAgent.NodeId} nodeId
559      */
560     toggleInlineVisibility: function(nodeId)
561     {
562         /**
563          * @param {WebInspector.CSSStyleDeclaration} inlineStyles
564          */
565         function callback(inlineStyles)
566         {
567             var visibility = inlineStyles.getLiveProperty("visibility");
568             if (visibility) {
569                 if (visibility.value === "hidden")
570                     visibility.setText("", false, true);
571                 else
572                     visibility.setValue("hidden", false, true);
573             } else
574                 inlineStyles.appendProperty("visibility", "hidden");
575         }
576
577         this.getInlineStylesAsync(nodeId, callback.bind(this));
578     },
579
580     __proto__: WebInspector.Object.prototype
581 }
582
583 /**
584  * @constructor
585  * @extends {WebInspector.LiveLocation}
586  * @param {WebInspector.CSSLocation} rawLocation
587  * @param {function(WebInspector.UILocation):(boolean|undefined)} updateDelegate
588  */
589 WebInspector.CSSStyleModel.LiveLocation = function(rawLocation, updateDelegate)
590 {
591     WebInspector.LiveLocation.call(this, rawLocation, updateDelegate);
592 }
593
594 WebInspector.CSSStyleModel.LiveLocation.prototype = {
595     /**
596      * @return {WebInspector.UILocation}
597      */
598     uiLocation: function()
599     {
600         var cssLocation = /** @type WebInspector.CSSLocation */ (this.rawLocation());
601         return WebInspector.cssModel.rawLocationToUILocation(cssLocation);
602     },
603
604     dispose: function()
605     {
606         WebInspector.LiveLocation.prototype.dispose.call(this);
607         var locations = WebInspector.cssModel._locations;
608         if (locations)
609             locations.remove(this);
610     },
611
612     __proto__: WebInspector.LiveLocation.prototype
613 }
614
615 /**
616  * @constructor
617  * @implements {WebInspector.RawLocation}
618  * @param {string} url
619  * @param {number} lineNumber
620  * @param {number=} columnNumber
621  */
622 WebInspector.CSSLocation = function(url, lineNumber, columnNumber)
623 {
624     this.url = url;
625     this.lineNumber = lineNumber;
626     this.columnNumber = columnNumber || 0;
627 }
628
629 /**
630  * @constructor
631  * @param {CSSAgent.CSSStyle} payload
632  */
633 WebInspector.CSSStyleDeclaration = function(payload)
634 {
635     this.id = payload.styleId;
636     this.width = payload.width;
637     this.height = payload.height;
638     this.range = payload.range;
639     this._shorthandValues = WebInspector.CSSStyleDeclaration.buildShorthandValueMap(payload.shorthandEntries);
640     this._livePropertyMap = {}; // LIVE properties (source-based or style-based) : { name -> CSSProperty }
641     this._allProperties = []; // ALL properties: [ CSSProperty ]
642     this.__disabledProperties = {}; // DISABLED properties: { index -> CSSProperty }
643     var payloadPropertyCount = payload.cssProperties.length;
644
645     var propertyIndex = 0;
646     for (var i = 0; i < payloadPropertyCount; ++i) {
647         var property = WebInspector.CSSProperty.parsePayload(this, i, payload.cssProperties[i]);
648         this._allProperties.push(property);
649         if (property.disabled)
650             this.__disabledProperties[i] = property;
651         if (!property.active && !property.styleBased)
652             continue;
653         var name = property.name;
654         this[propertyIndex] = name;
655         this._livePropertyMap[name] = property;
656         ++propertyIndex;
657     }
658     this.length = propertyIndex;
659     if ("cssText" in payload)
660         this.cssText = payload.cssText;
661 }
662
663 /**
664  * @param {Array.<CSSAgent.ShorthandEntry>} shorthandEntries
665  * @return {Object}
666  */
667 WebInspector.CSSStyleDeclaration.buildShorthandValueMap = function(shorthandEntries)
668 {
669     var result = {};
670     for (var i = 0; i < shorthandEntries.length; ++i)
671         result[shorthandEntries[i].name] = shorthandEntries[i].value;
672     return result;
673 }
674
675 /**
676  * @param {CSSAgent.CSSStyle} payload
677  * @return {WebInspector.CSSStyleDeclaration}
678  */
679 WebInspector.CSSStyleDeclaration.parsePayload = function(payload)
680 {
681     return new WebInspector.CSSStyleDeclaration(payload);
682 }
683
684 /**
685  * @param {Array.<CSSAgent.CSSComputedStyleProperty>} payload
686  * @return {WebInspector.CSSStyleDeclaration}
687  */
688 WebInspector.CSSStyleDeclaration.parseComputedStylePayload = function(payload)
689 {
690     var newPayload = /** @type {CSSAgent.CSSStyle} */ ({ cssProperties: [], shorthandEntries: [], width: "", height: "" });
691     if (payload)
692         newPayload.cssProperties = payload;
693
694     return new WebInspector.CSSStyleDeclaration(newPayload);
695 }
696
697 WebInspector.CSSStyleDeclaration.prototype = {
698     get allProperties()
699     {
700         return this._allProperties;
701     },
702
703     /**
704      * @param {string} name
705      * @return {WebInspector.CSSProperty|undefined}
706      */
707     getLiveProperty: function(name)
708     {
709         return this._livePropertyMap[name];
710     },
711
712     /**
713      * @param {string} name
714      * @return {string}
715      */
716     getPropertyValue: function(name)
717     {
718         var property = this._livePropertyMap[name];
719         return property ? property.value : "";
720     },
721
722     /**
723      * @param {string} name
724      * @return {string}
725      */
726     getPropertyPriority: function(name)
727     {
728         var property = this._livePropertyMap[name];
729         return property ? property.priority : "";
730     },
731
732     /**
733      * @param {string} name
734      * @return {boolean}
735      */
736     isPropertyImplicit: function(name)
737     {
738         var property = this._livePropertyMap[name];
739         return property ? property.implicit : "";
740     },
741
742     /**
743      * @param {string} name
744      * @return {Array.<WebInspector.CSSProperty>}
745      */
746     longhandProperties: function(name)
747     {
748         var longhands = WebInspector.CSSMetadata.cssPropertiesMetainfo.longhands(name);
749         var result = [];
750         for (var i = 0; longhands && i < longhands.length; ++i) {
751             var property = this._livePropertyMap[longhands[i]];
752             if (property)
753                 result.push(property);
754         }
755         return result;
756     },
757
758     /**
759      * @param {string} shorthandProperty
760      * @return {string}
761      */
762     shorthandValue: function(shorthandProperty)
763     {
764         return this._shorthandValues[shorthandProperty];
765     },
766
767     /**
768      * @param {number} index
769      * @return {?WebInspector.CSSProperty}
770      */
771     propertyAt: function(index)
772     {
773         return (index < this.allProperties.length) ? this.allProperties[index] : null;
774     },
775
776     /**
777      * @return {number}
778      */
779     pastLastSourcePropertyIndex: function()
780     {
781         for (var i = this.allProperties.length - 1; i >= 0; --i) {
782             var property = this.allProperties[i];
783             if (property.active || property.disabled)
784                 return i + 1;
785         }
786         return 0;
787     },
788
789     /**
790      * @param {number=} index
791      */
792     newBlankProperty: function(index)
793     {
794         index = (typeof index === "undefined") ? this.pastLastSourcePropertyIndex() : index;
795         return new WebInspector.CSSProperty(this, index, "", "", "", "active", true, false, "");
796     },
797
798     /**
799      * @param {number} index
800      * @param {string} name
801      * @param {string} value
802      * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback
803      */
804     insertPropertyAt: function(index, name, value, userCallback)
805     {
806         /**
807          * @param {?string} error
808          * @param {CSSAgent.CSSStyle} payload
809          */
810         function callback(error, payload)
811         {
812             WebInspector.cssModel._pendingCommandsMajorState.pop();
813             if (!userCallback)
814                 return;
815
816             if (error) {
817                 console.error(error);
818                 userCallback(null);
819             } else {
820                 userCallback(WebInspector.CSSStyleDeclaration.parsePayload(payload));
821             }
822         }
823
824         if (!this.id)
825             throw "No style id";
826
827         WebInspector.cssModel._pendingCommandsMajorState.push(true);
828         CSSAgent.setPropertyText(this.id, index, name + ": " + value + ";", false, callback.bind(this));
829     },
830
831     /**
832      * @param {string} name
833      * @param {string} value
834      * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback
835      */
836     appendProperty: function(name, value, userCallback)
837     {
838         this.insertPropertyAt(this.allProperties.length, name, value, userCallback);
839     }
840 }
841
842 /**
843  * @constructor
844  * @param {CSSAgent.CSSRule} payload
845  * @param {Array.<number>=} matchingSelectors
846  */
847 WebInspector.CSSRule = function(payload, matchingSelectors)
848 {
849     this.id = payload.ruleId;
850     if (matchingSelectors)
851         this.matchingSelectors = matchingSelectors;
852     this.selectors = payload.selectorList.selectors;
853     this.selectorText = this.selectors.join(", ");
854     this.selectorRange = payload.selectorList.range;
855     this.sourceLine = payload.sourceLine;
856     this.sourceURL = payload.sourceURL;
857     if (payload.sourceURL)
858         this._rawLocation = new WebInspector.CSSLocation(payload.sourceURL, payload.sourceLine);
859     this.origin = payload.origin;
860     this.style = WebInspector.CSSStyleDeclaration.parsePayload(payload.style);
861     this.style.parentRule = this;
862     if (payload.media)
863         this.media = WebInspector.CSSMedia.parseMediaArrayPayload(payload.media);
864 }
865
866 /**
867  * @param {CSSAgent.CSSRule} payload
868  * @param {Array.<number>=} matchingIndices
869  * @return {WebInspector.CSSRule}
870  */
871 WebInspector.CSSRule.parsePayload = function(payload, matchingIndices)
872 {
873     return new WebInspector.CSSRule(payload, matchingIndices);
874 }
875
876 WebInspector.CSSRule.prototype = {
877     get isUserAgent()
878     {
879         return this.origin === "user-agent";
880     },
881
882     get isUser()
883     {
884         return this.origin === "user";
885     },
886
887     get isViaInspector()
888     {
889         return this.origin === "inspector";
890     },
891
892     get isRegular()
893     {
894         return this.origin === "regular";
895     }
896 }
897
898 /**
899  * @constructor
900  * @param {?WebInspector.CSSStyleDeclaration} ownerStyle
901  * @param {number} index
902  * @param {string} name
903  * @param {string} value
904  * @param {?string} priority
905  * @param {string} status
906  * @param {boolean} parsedOk
907  * @param {boolean} implicit
908  * @param {?string=} text
909  * @param {CSSAgent.SourceRange=} range
910  */
911 WebInspector.CSSProperty = function(ownerStyle, index, name, value, priority, status, parsedOk, implicit, text, range)
912 {
913     this.ownerStyle = ownerStyle;
914     this.index = index;
915     this.name = name;
916     this.value = value;
917     this.priority = priority;
918     this.status = status;
919     this.parsedOk = parsedOk;
920     this.implicit = implicit;
921     this.text = text;
922     this.range = range;
923 }
924
925 /**
926  * @param {?WebInspector.CSSStyleDeclaration} ownerStyle
927  * @param {number} index
928  * @param {CSSAgent.CSSProperty} payload
929  * @return {WebInspector.CSSProperty}
930  */
931 WebInspector.CSSProperty.parsePayload = function(ownerStyle, index, payload)
932 {
933     // The following default field values are used in the payload:
934     // priority: ""
935     // parsedOk: true
936     // implicit: false
937     // status: "style"
938     var result = new WebInspector.CSSProperty(
939         ownerStyle, index, payload.name, payload.value, payload.priority || "", payload.status || "style", ("parsedOk" in payload) ? !!payload.parsedOk : true, !!payload.implicit, payload.text, payload.range);
940     return result;
941 }
942
943 WebInspector.CSSProperty.prototype = {
944     get propertyText()
945     {
946         if (this.text !== undefined)
947             return this.text;
948
949         if (this.name === "")
950             return "";
951         return this.name + ": " + this.value + (this.priority ? " !" + this.priority : "") + ";";
952     },
953
954     get isLive()
955     {
956         return this.active || this.styleBased;
957     },
958
959     get active()
960     {
961         return this.status === "active";
962     },
963
964     get styleBased()
965     {
966         return this.status === "style";
967     },
968
969     get inactive()
970     {
971         return this.status === "inactive";
972     },
973
974     get disabled()
975     {
976         return this.status === "disabled";
977     },
978
979     /**
980      * Replaces "propertyName: propertyValue [!important];" in the stylesheet by an arbitrary propertyText.
981      *
982      * @param {string} propertyText
983      * @param {boolean} majorChange
984      * @param {boolean} overwrite
985      * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback
986      */
987     setText: function(propertyText, majorChange, overwrite, userCallback)
988     {
989         /**
990          * @param {?WebInspector.CSSStyleDeclaration} style
991          */
992         function enabledCallback(style)
993         {
994             if (userCallback)
995                 userCallback(style);
996         }
997
998         /**
999          * @param {?string} error
1000          * @param {?CSSAgent.CSSStyle} stylePayload
1001          */
1002         function callback(error, stylePayload)
1003         {
1004             WebInspector.cssModel._pendingCommandsMajorState.pop();
1005             if (!error) {
1006                 if (majorChange)
1007                     WebInspector.domAgent.markUndoableState();
1008                 this.text = propertyText;
1009                 var style = WebInspector.CSSStyleDeclaration.parsePayload(stylePayload);
1010                 var newProperty = style.allProperties[this.index];
1011
1012                 if (newProperty && this.disabled && !propertyText.match(/^\s*$/)) {
1013                     newProperty.setDisabled(false, enabledCallback);
1014                     return;
1015                 }
1016
1017                 if (userCallback)
1018                     userCallback(style);
1019             } else {
1020                 if (userCallback)
1021                     userCallback(null);
1022             }
1023         }
1024
1025         if (!this.ownerStyle)
1026             throw "No ownerStyle for property";
1027
1028         if (!this.ownerStyle.id)
1029             throw "No owner style id";
1030
1031         // An index past all the properties adds a new property to the style.
1032         WebInspector.cssModel._pendingCommandsMajorState.push(majorChange);
1033         CSSAgent.setPropertyText(this.ownerStyle.id, this.index, propertyText, overwrite, callback.bind(this));
1034     },
1035
1036     /**
1037      * @param {string} newValue
1038      * @param {boolean} majorChange
1039      * @param {boolean} overwrite
1040      * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback
1041      */
1042     setValue: function(newValue, majorChange, overwrite, userCallback)
1043     {
1044         var text = this.name + ": " + newValue + (this.priority ? " !" + this.priority : "") + ";"
1045         this.setText(text, majorChange, overwrite, userCallback);
1046     },
1047
1048     /**
1049      * @param {boolean} disabled
1050      * @param {function(?WebInspector.CSSStyleDeclaration)=} userCallback
1051      */
1052     setDisabled: function(disabled, userCallback)
1053     {
1054         if (!this.ownerStyle && userCallback)
1055             userCallback(null);
1056         if (disabled === this.disabled && userCallback)
1057             userCallback(this.ownerStyle);
1058
1059         /**
1060          * @param {?string} error
1061          * @param {CSSAgent.CSSStyle} stylePayload
1062          */
1063         function callback(error, stylePayload)
1064         {
1065             WebInspector.cssModel._pendingCommandsMajorState.pop();
1066             if (error) {
1067                 if (userCallback)
1068                     userCallback(null);
1069                 return;
1070             }
1071             WebInspector.domAgent.markUndoableState();
1072             if (userCallback) {
1073                 var style = WebInspector.CSSStyleDeclaration.parsePayload(stylePayload);
1074                 userCallback(style);
1075             }
1076         }
1077
1078         if (!this.ownerStyle.id)
1079             throw "No owner style id";
1080
1081         WebInspector.cssModel._pendingCommandsMajorState.push(false);
1082         CSSAgent.toggleProperty(this.ownerStyle.id, this.index, disabled, callback.bind(this));
1083     }
1084 }
1085
1086 /**
1087  * @constructor
1088  * @param {CSSAgent.CSSMedia} payload
1089  */
1090 WebInspector.CSSMedia = function(payload)
1091 {
1092     this.text = payload.text;
1093     this.source = payload.source;
1094     this.sourceURL = payload.sourceURL || "";
1095     this.sourceLine = typeof payload.sourceLine === "undefined" || this.source === "linkedSheet" ? -1 : payload.sourceLine;
1096 }
1097
1098 WebInspector.CSSMedia.Source = {
1099     LINKED_SHEET: "linkedSheet",
1100     INLINE_SHEET: "inlineSheet",
1101     MEDIA_RULE: "mediaRule",
1102     IMPORT_RULE: "importRule"
1103 };
1104
1105 /**
1106  * @param {CSSAgent.CSSMedia} payload
1107  * @return {WebInspector.CSSMedia}
1108  */
1109 WebInspector.CSSMedia.parsePayload = function(payload)
1110 {
1111     return new WebInspector.CSSMedia(payload);
1112 }
1113
1114 /**
1115  * @param {Array.<CSSAgent.CSSMedia>} payload
1116  * @return {Array.<WebInspector.CSSMedia>}
1117  */
1118 WebInspector.CSSMedia.parseMediaArrayPayload = function(payload)
1119 {
1120     var result = [];
1121     for (var i = 0; i < payload.length; ++i)
1122         result.push(WebInspector.CSSMedia.parsePayload(payload[i]));
1123     return result;
1124 }
1125
1126 /**
1127  * @constructor
1128  * @param {CSSAgent.CSSStyleSheetBody} payload
1129  */
1130 WebInspector.CSSStyleSheet = function(payload)
1131 {
1132     this.id = payload.styleSheetId;
1133     this.rules = [];
1134     this.styles = {};
1135     for (var i = 0; i < payload.rules.length; ++i) {
1136         var rule = WebInspector.CSSRule.parsePayload(payload.rules[i]);
1137         this.rules.push(rule);
1138         if (rule.style)
1139             this.styles[rule.style.id] = rule.style;
1140     }
1141     if ("text" in payload)
1142         this._text = payload.text;
1143 }
1144
1145 /**
1146  * @param {CSSAgent.StyleSheetId} styleSheetId
1147  * @param {function(?WebInspector.CSSStyleSheet)} userCallback
1148  */
1149 WebInspector.CSSStyleSheet.createForId = function(styleSheetId, userCallback)
1150 {
1151     /**
1152      * @param {?string} error
1153      * @param {CSSAgent.CSSStyleSheetBody} styleSheetPayload
1154      */
1155     function callback(error, styleSheetPayload)
1156     {
1157         if (error)
1158             userCallback(null);
1159         else
1160             userCallback(new WebInspector.CSSStyleSheet(styleSheetPayload));
1161     }
1162     CSSAgent.getStyleSheet(styleSheetId, callback.bind(this));
1163 }
1164
1165 WebInspector.CSSStyleSheet.prototype = {
1166     /**
1167      * @return {string|undefined}
1168      */
1169     getText: function()
1170     {
1171         return this._text;
1172     },
1173
1174     /**
1175      * @param {string} newText
1176      * @param {boolean} majorChange
1177      * @param {function(?string)=} userCallback
1178      */
1179     setText: function(newText, majorChange, userCallback)
1180     {
1181         /**
1182          * @param {?string} error
1183          */
1184         function callback(error)
1185         {
1186             if (!error)
1187                 WebInspector.domAgent.markUndoableState();
1188
1189             WebInspector.cssModel._pendingCommandsMajorState.pop();
1190             if (userCallback)
1191                 userCallback(error);
1192         }
1193
1194         WebInspector.cssModel._pendingCommandsMajorState.push(majorChange);
1195         CSSAgent.setStyleSheetText(this.id, newText, callback.bind(this));
1196     }
1197 }
1198
1199 /**
1200  * @constructor
1201  */
1202 WebInspector.CSSStyleModelResourceBinding = function()
1203 {
1204     this._reset();
1205 }
1206
1207 WebInspector.CSSStyleModelResourceBinding.prototype = {
1208     /**
1209      * @param {WebInspector.Resource} resource
1210      * @param {function(?CSSAgent.StyleSheetId)} callback
1211      */
1212     requestStyleSheetIdForResource: function(resource, callback)
1213     {
1214         function innerCallback()
1215         {
1216             callback(this._styleSheetIdForResource(resource));
1217         }
1218         
1219         if (this._styleSheetIdForResource(resource))
1220             innerCallback.call(this);
1221         else
1222             this._loadStyleSheetHeaders(innerCallback.bind(this));
1223     },
1224
1225     /**
1226      * @param {CSSAgent.StyleSheetId} styleSheetId
1227      * @param {function(?string)} callback
1228      */
1229     requestResourceURLForStyleSheetId: function(styleSheetId, callback)
1230     {
1231         function innerCallback()
1232         {
1233             var header = this._styleSheetIdToHeader[styleSheetId];
1234             if (!header) {
1235                 callback(null);
1236                 return;
1237             }
1238
1239             var frame = WebInspector.resourceTreeModel.frameForId(header.frameId);
1240             if (!frame) {
1241                 callback(null);
1242                 return;
1243             }
1244
1245             var styleSheetURL = header.origin === "inspector" ? this._viaInspectorResourceURL(header.sourceURL) : header.sourceURL;
1246             callback(styleSheetURL);
1247         }
1248         
1249         if (this._styleSheetIdToHeader[styleSheetId])
1250             innerCallback.call(this);
1251         else
1252             this._loadStyleSheetHeaders(innerCallback.bind(this));
1253     },
1254
1255     /**
1256      * @param {WebInspector.Resource} resource
1257      * @return {CSSAgent.StyleSheetId}
1258      */
1259     _styleSheetIdForResource: function(resource)
1260     {
1261         return this._frameAndURLToStyleSheetId[resource.frameId + ":" + resource.url];
1262     },
1263
1264     /**
1265      * @param {function(?string)} callback
1266      */
1267     _loadStyleSheetHeaders: function(callback)
1268     {
1269         /**
1270          * @param {?string} error
1271          * @param {Array.<CSSAgent.CSSStyleSheetHeader>} infos
1272          */
1273         function didGetAllStyleSheets(error, infos)
1274         {
1275             if (error) {
1276                 callback(error);
1277                 return;
1278             }
1279
1280             for (var i = 0; i < infos.length; ++i) {
1281                 var info = infos[i];
1282                 if (info.origin === "inspector") {
1283                     this._getOrCreateInspectorResource(info);
1284                     continue;
1285                 }
1286                 this._frameAndURLToStyleSheetId[info.frameId + ":" + info.sourceURL] = info.styleSheetId;
1287                 this._styleSheetIdToHeader[info.styleSheetId] = info;
1288             }
1289             callback(null);
1290         }
1291         CSSAgent.getAllStyleSheets(didGetAllStyleSheets.bind(this));
1292     },
1293
1294     /**
1295      * @param {CSSAgent.StyleSheetId} styleSheetId
1296      * @param {function(?WebInspector.Resource)} callback
1297      */
1298     _requestViaInspectorResource: function(styleSheetId, callback)
1299     {
1300         var header = this._styleSheetIdToHeader[styleSheetId];
1301         if (header) {
1302             callback(this._getOrCreateInspectorResource(header));
1303             return;
1304         }
1305
1306         function headersLoaded()
1307         {
1308             var header = this._styleSheetIdToHeader[styleSheetId];
1309             if (header)
1310                 callback(this._getOrCreateInspectorResource(header));
1311             else
1312                 callback(null);
1313         }
1314         this._loadStyleSheetHeaders(headersLoaded.bind(this));
1315     },
1316
1317     /**
1318      * @param {CSSAgent.CSSStyleSheetHeader} header
1319      * @return {?WebInspector.Resource}
1320      */
1321     _getOrCreateInspectorResource: function(header)
1322     {
1323         var frame = WebInspector.resourceTreeModel.frameForId(header.frameId);
1324         if (!frame)
1325             return null;
1326
1327         var viaInspectorURL = this._viaInspectorResourceURL(header.sourceURL);    
1328         var inspectorResource = frame.resourceForURL(viaInspectorURL);
1329         if (inspectorResource)
1330             return inspectorResource;
1331
1332         var resource = frame.resourceForURL(header.sourceURL);
1333         if (!resource)
1334             return null;
1335
1336         this._frameAndURLToStyleSheetId[header.frameId + ":" + viaInspectorURL] = header.styleSheetId;
1337         this._styleSheetIdToHeader[header.styleSheetId] = header;
1338         inspectorResource = new WebInspector.Resource(null, viaInspectorURL, resource.documentURL, resource.frameId, resource.loaderId, WebInspector.resourceTypes.Stylesheet, "text/css", true);
1339         /**
1340          * @param {function(?string, boolean, string)} callback
1341          */
1342         function overrideRequestContent(callback)
1343         {
1344             function callbackWrapper(error, content)
1345             {
1346                 callback(error ? "" : content, false, "text/css");
1347             }
1348             CSSAgent.getStyleSheetText(header.styleSheetId, callbackWrapper);
1349         }
1350         inspectorResource.requestContent = overrideRequestContent;
1351         frame.addResource(inspectorResource);
1352         return inspectorResource;
1353     },
1354
1355     /**
1356      * @param {string} documentURL
1357      * @return {string}
1358      */
1359     _viaInspectorResourceURL: function(documentURL)
1360     {
1361         var parsedURL = new WebInspector.ParsedURL(documentURL);
1362         var fakeURL = "inspector://" + parsedURL.host + parsedURL.folderPathComponents;
1363         if (!fakeURL.endsWith("/"))
1364             fakeURL += "/";
1365         fakeURL += "inspector-stylesheet";
1366         return fakeURL;
1367     },
1368
1369     _reset: function()
1370     {
1371         // Main frame navigation - clear history.
1372         this._frameAndURLToStyleSheetId = {};
1373         this._styleSheetIdToHeader = {};
1374     }
1375 }
1376
1377 /**
1378  * @constructor
1379  * @implements {CSSAgent.Dispatcher}
1380  * @param {WebInspector.CSSStyleModel} cssModel
1381  */
1382 WebInspector.CSSDispatcher = function(cssModel)
1383 {
1384     this._cssModel = cssModel;
1385 }
1386
1387 WebInspector.CSSDispatcher.prototype = {
1388     mediaQueryResultChanged: function()
1389     {
1390         this._cssModel.mediaQueryResultChanged();
1391     },
1392
1393     /**
1394      * @param {CSSAgent.StyleSheetId} styleSheetId
1395      */
1396     styleSheetChanged: function(styleSheetId)
1397     {
1398         this._cssModel._fireStyleSheetChanged(styleSheetId);
1399     },
1400
1401     /**
1402      * @param {CSSAgent.NamedFlow} namedFlowPayload
1403      */
1404     namedFlowCreated: function(namedFlowPayload)
1405     {
1406         this._cssModel._namedFlowCreated(namedFlowPayload);
1407     },
1408
1409     /**
1410      * @param {DOMAgent.NodeId} documentNodeId
1411      * @param {string} flowName
1412      */
1413     namedFlowRemoved: function(documentNodeId, flowName)
1414     {
1415         this._cssModel._namedFlowRemoved(documentNodeId, flowName);
1416     },
1417
1418     /**
1419      * @param {CSSAgent.NamedFlow} namedFlowPayload
1420      */
1421     regionLayoutUpdated: function(namedFlowPayload)
1422     {
1423         this._cssModel._regionLayoutUpdated(namedFlowPayload);
1424     }
1425 }
1426
1427 /**
1428  * @constructor
1429  * @param {CSSAgent.NamedFlow} payload
1430  */
1431 WebInspector.NamedFlow = function(payload)
1432 {
1433     this.documentNodeId = payload.documentNodeId;
1434     this.name = payload.name;
1435     this.overset = payload.overset;
1436     this.content = payload.content;
1437     this.regions = payload.regions;
1438 }
1439
1440 /**
1441  * @param {CSSAgent.NamedFlow} payload
1442  * @return {WebInspector.NamedFlow}
1443  */
1444 WebInspector.NamedFlow.parsePayload = function(payload)
1445 {
1446     return new WebInspector.NamedFlow(payload);
1447 }
1448
1449 /**
1450  * @constructor
1451  * @param {Array.<CSSAgent.NamedFlow>} payload
1452  */
1453 WebInspector.NamedFlowCollection = function(payload)
1454 {
1455     /** @type {Object.<string, WebInspector.NamedFlow>} */
1456     this.namedFlowMap = {};
1457
1458     for (var i = 0; i < payload.length; ++i) {
1459         var namedFlow = WebInspector.NamedFlow.parsePayload(payload[i]);
1460         this.namedFlowMap[namedFlow.name] = namedFlow;
1461     }
1462 }
1463
1464 WebInspector.NamedFlowCollection.prototype = {
1465     /**
1466      * @param {WebInspector.NamedFlow} namedFlow
1467      */
1468     _appendNamedFlow: function(namedFlow)
1469     {
1470         this.namedFlowMap[namedFlow.name] = namedFlow;
1471     },
1472
1473     /**
1474      * @param {string} flowName
1475      */
1476     _removeNamedFlow: function(flowName)
1477     {
1478         delete this.namedFlowMap[flowName];
1479     },
1480
1481     /**
1482      * @param {string} flowName
1483      * @return {WebInspector.NamedFlow}
1484      */
1485     flowByName: function(flowName)
1486     {
1487         var namedFlow = this.namedFlowMap[flowName];
1488
1489         if (!namedFlow)
1490             return null;
1491         return namedFlow;
1492     }
1493 }
1494 /**
1495  * @type {WebInspector.CSSStyleModel}
1496  */
1497 WebInspector.cssModel = null;