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