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