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