• 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
31WebInspector.CSSStyleModel = function()
32{
33    new WebInspector.CSSStyleModelResourceBinding(this);
34}
35
36WebInspector.CSSStyleModel.parseRuleArrayPayload = function(ruleArray)
37{
38    var result = [];
39    for (var i = 0; i < ruleArray.length; ++i)
40        result.push(WebInspector.CSSRule.parsePayload(ruleArray[i]));
41    return result;
42}
43
44WebInspector.CSSStyleModel.Events = {
45    StyleSheetChanged: 0
46}
47
48WebInspector.CSSStyleModel.prototype = {
49    getStylesAsync: function(nodeId, userCallback)
50    {
51        function callback(userCallback, error, payload)
52        {
53            if (error) {
54                if (userCallback)
55                    userCallback(null);
56                return;
57            }
58
59            var result = {};
60            if ("inlineStyle" in payload)
61                result.inlineStyle = WebInspector.CSSStyleDeclaration.parsePayload(payload.inlineStyle);
62
63            result.computedStyle = WebInspector.CSSStyleDeclaration.parsePayload(payload.computedStyle);
64            result.matchedCSSRules = WebInspector.CSSStyleModel.parseRuleArrayPayload(payload.matchedCSSRules);
65
66            result.styleAttributes = {};
67            var payloadStyleAttributes = payload.styleAttributes;
68            for (var i = 0; i < payloadStyleAttributes.length; ++i) {
69                var name = payloadStyleAttributes[i].name;
70                result.styleAttributes[name] = WebInspector.CSSStyleDeclaration.parsePayload(payloadStyleAttributes[i].style);
71            }
72
73            result.pseudoElements = [];
74            for (var i = 0; i < payload.pseudoElements.length; ++i) {
75                var entryPayload = payload.pseudoElements[i];
76                result.pseudoElements.push({ pseudoId: entryPayload.pseudoId, rules: WebInspector.CSSStyleModel.parseRuleArrayPayload(entryPayload.rules) });
77            }
78
79            result.inherited = [];
80            for (var i = 0; i < payload.inherited.length; ++i) {
81                var entryPayload = payload.inherited[i];
82                var entry = {};
83                if ("inlineStyle" in entryPayload)
84                    entry.inlineStyle = WebInspector.CSSStyleDeclaration.parsePayload(entryPayload.inlineStyle);
85                if ("matchedCSSRules" in entryPayload)
86                    entry.matchedCSSRules = WebInspector.CSSStyleModel.parseRuleArrayPayload(entryPayload.matchedCSSRules);
87                result.inherited.push(entry);
88            }
89
90            if (userCallback)
91                userCallback(result);
92        }
93
94        CSSAgent.getStylesForNode(nodeId, callback.bind(null, userCallback));
95    },
96
97    getComputedStyleAsync: function(nodeId, userCallback)
98    {
99        function callback(userCallback, error, stylePayload)
100        {
101            if (error)
102                userCallback(null);
103            else
104                userCallback(WebInspector.CSSStyleDeclaration.parsePayload(stylePayload));
105        }
106
107        CSSAgent.getComputedStyleForNode(nodeId, callback.bind(null, userCallback));
108    },
109
110    getInlineStyleAsync: function(nodeId, userCallback)
111    {
112        function callback(userCallback, error, stylePayload)
113        {
114            if (error)
115                userCallback(null);
116            else
117                userCallback(WebInspector.CSSStyleDeclaration.parsePayload(stylePayload));
118        }
119
120        CSSAgent.getInlineStyleForNode(nodeId, callback.bind(null, userCallback));
121    },
122
123    setRuleSelector: function(ruleId, nodeId, newSelector, successCallback, failureCallback)
124    {
125        function checkAffectsCallback(nodeId, successCallback, rulePayload, selectedNodeIds)
126        {
127            if (!selectedNodeIds)
128                return;
129            var doesAffectSelectedNode = (selectedNodeIds.indexOf(nodeId) >= 0);
130            var rule = WebInspector.CSSRule.parsePayload(rulePayload);
131            successCallback(rule, doesAffectSelectedNode);
132            this._fireStyleSheetChanged(rule.id.styleSheetId, true);
133        }
134
135        function callback(nodeId, successCallback, failureCallback, error, newSelector, rulePayload)
136        {
137            // FIXME: looks like rulePayload is always null.
138            if (error)
139                failureCallback();
140            else {
141                var documentElementId = this._documentElementId(nodeId);
142                if (documentElementId)
143                    WebInspector.domAgent.querySelectorAll(documentElementId, newSelector, checkAffectsCallback.bind(this, nodeId, successCallback, rulePayload));
144                else
145                    failureCallback();
146            }
147        }
148
149        CSSAgent.setRuleSelector(ruleId, newSelector, callback.bind(this, nodeId, successCallback, failureCallback, newSelector));
150    },
151
152    addRule: function(nodeId, selector, successCallback, failureCallback)
153    {
154        function checkAffectsCallback(nodeId, successCallback, rulePayload, selectedNodeIds)
155        {
156            if (!selectedNodeIds)
157                return;
158            var doesAffectSelectedNode = (selectedNodeIds.indexOf(nodeId) >= 0);
159            var rule = WebInspector.CSSRule.parsePayload(rulePayload);
160            successCallback(rule, doesAffectSelectedNode);
161            this._fireStyleSheetChanged(rule.id.styleSheetId, true);
162        }
163
164        function callback(successCallback, failureCallback, selector, error, rulePayload)
165        {
166            if (error) {
167                // Invalid syntax for a selector
168                failureCallback();
169            } else {
170                var documentElementId = this._documentElementId(nodeId);
171                if (documentElementId)
172                    WebInspector.domAgent.querySelectorAll(documentElementId, selector, checkAffectsCallback.bind(this, nodeId, successCallback, rulePayload));
173                else
174                    failureCallback();
175            }
176        }
177
178        CSSAgent.addRule(nodeId, selector, callback.bind(this, successCallback, failureCallback, selector));
179    },
180
181    _documentElementId: function(nodeId)
182    {
183        var node = WebInspector.domAgent.nodeForId(nodeId);
184        if (!node)
185            return null;
186        return node.ownerDocumentElement().id;
187    },
188
189    _fireStyleSheetChanged: function(styleSheetId, majorChange, callback)
190    {
191        callback = callback || function() {};
192
193        if (!majorChange || !styleSheetId || !this.hasEventListeners(WebInspector.CSSStyleModel.Events.StyleSheetChanged)) {
194            callback();
195            return;
196        }
197
198        function mycallback(error, content)
199        {
200            if (!error)
201                this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.StyleSheetChanged, { styleSheetId: styleSheetId, content: content, majorChange: majorChange });
202            callback();
203        }
204
205        CSSAgent.getStyleSheetText(styleSheetId, mycallback.bind(this));
206    },
207
208    setStyleSheetText: function(styleSheetId, newText, majorChange, userCallback)
209    {
210        function callback(error)
211        {
212             if (!error)
213                 this._fireStyleSheetChanged(styleSheetId, majorChange, userCallback ? userCallback.bind(this, error) : null);
214        }
215        CSSAgent.setStyleSheetText(styleSheetId, newText, callback.bind(this));
216    }
217}
218
219WebInspector.CSSStyleModel.prototype.__proto__ = WebInspector.Object.prototype;
220
221WebInspector.CSSStyleDeclaration = function(payload)
222{
223    this.id = payload.styleId;
224    this.width = payload.width;
225    this.height = payload.height;
226    this.range = payload.range;
227    this._shorthandValues = WebInspector.CSSStyleDeclaration.buildShorthandValueMap(payload.shorthandEntries);
228    this._livePropertyMap = {}; // LIVE properties (source-based or style-based) : { name -> CSSProperty }
229    this._allProperties = []; // ALL properties: [ CSSProperty ]
230    this._longhandProperties = {}; // shorthandName -> [ CSSProperty ]
231    this.__disabledProperties = {}; // DISABLED properties: { index -> CSSProperty }
232    var payloadPropertyCount = payload.cssProperties.length;
233
234    var propertyIndex = 0;
235    for (var i = 0; i < payloadPropertyCount; ++i) {
236        var property = new WebInspector.CSSProperty.parsePayload(this, i, payload.cssProperties[i]);
237        this._allProperties.push(property);
238        if (property.disabled)
239            this.__disabledProperties[i] = property;
240        if (!property.active && !property.styleBased)
241            continue;
242        var name = property.name;
243        this[propertyIndex] = name;
244        this._livePropertyMap[name] = property;
245
246        // Index longhand properties.
247        if (property.shorthand) { // only for parsed
248            var longhands = this._longhandProperties[property.shorthand];
249            if (!longhands) {
250                longhands = [];
251                this._longhandProperties[property.shorthand] = longhands;
252            }
253            longhands.push(property);
254        }
255        ++propertyIndex;
256    }
257    this.length = propertyIndex;
258    if ("cssText" in payload)
259        this.cssText = payload.cssText;
260}
261
262WebInspector.CSSStyleDeclaration.buildShorthandValueMap = function(shorthandEntries)
263{
264    var result = {};
265    for (var i = 0; i < shorthandEntries.length; ++i)
266        result[shorthandEntries[i].name] = shorthandEntries[i].value;
267    return result;
268}
269
270WebInspector.CSSStyleDeclaration.parsePayload = function(payload)
271{
272    return new WebInspector.CSSStyleDeclaration(payload);
273}
274
275WebInspector.CSSStyleDeclaration.prototype = {
276    get allProperties()
277    {
278        return this._allProperties;
279    },
280
281    getLiveProperty: function(name)
282    {
283        return this._livePropertyMap[name];
284    },
285
286    getPropertyValue: function(name)
287    {
288        var property = this._livePropertyMap[name];
289        return property ? property.value : "";
290    },
291
292    getPropertyPriority: function(name)
293    {
294        var property = this._livePropertyMap[name];
295        return property ? property.priority : "";
296    },
297
298    getPropertyShorthand: function(name)
299    {
300        var property = this._livePropertyMap[name];
301        return property ? property.shorthand : "";
302    },
303
304    isPropertyImplicit: function(name)
305    {
306        var property = this._livePropertyMap[name];
307        return property ? property.implicit : "";
308    },
309
310    styleTextWithShorthands: function()
311    {
312        var cssText = "";
313        var foundProperties = {};
314        for (var i = 0; i < this.length; ++i) {
315            var individualProperty = this[i];
316            var shorthandProperty = this.getPropertyShorthand(individualProperty);
317            var propertyName = (shorthandProperty || individualProperty);
318
319            if (propertyName in foundProperties)
320                continue;
321
322            if (shorthandProperty) {
323                var value = this.getShorthandValue(shorthandProperty);
324                var priority = this.getShorthandPriority(shorthandProperty);
325            } else {
326                var value = this.getPropertyValue(individualProperty);
327                var priority = this.getPropertyPriority(individualProperty);
328            }
329
330            foundProperties[propertyName] = true;
331
332            cssText += propertyName + ": " + value;
333            if (priority)
334                cssText += " !" + priority;
335            cssText += "; ";
336        }
337
338        return cssText;
339    },
340
341    getLonghandProperties: function(name)
342    {
343        return this._longhandProperties[name] || [];
344    },
345
346    getShorthandValue: function(shorthandProperty)
347    {
348        var property = this.getLiveProperty(shorthandProperty);
349        return property ? property.value : this._shorthandValues[shorthandProperty];
350    },
351
352    getShorthandPriority: function(shorthandProperty)
353    {
354        var priority = this.getPropertyPriority(shorthandProperty);
355        if (priority)
356            return priority;
357
358        var longhands = this._longhandProperties[shorthandProperty];
359        return longhands ? this.getPropertyPriority(longhands[0]) : null;
360    },
361
362    propertyAt: function(index)
363    {
364        return (index < this.allProperties.length) ? this.allProperties[index] : null;
365    },
366
367    pastLastSourcePropertyIndex: function()
368    {
369        for (var i = this.allProperties.length - 1; i >= 0; --i) {
370            var property = this.allProperties[i];
371            if (property.active || property.disabled)
372                return i + 1;
373        }
374        return 0;
375    },
376
377    newBlankProperty: function()
378    {
379        return new WebInspector.CSSProperty(this, this.pastLastSourcePropertyIndex(), "", "", "", "active", true, false, false, "");
380    },
381
382    insertPropertyAt: function(index, name, value, userCallback)
383    {
384        function callback(userCallback, error, payload)
385        {
386            if (!userCallback)
387                return;
388
389            if (error) {
390                console.error(JSON.stringify(error));
391                userCallback(null);
392            } else {
393                userCallback(WebInspector.CSSStyleDeclaration.parsePayload(payload));
394                WebInspector.cssModel._fireStyleSheetChanged(this.id.styleSheetId, true);
395            }
396        }
397
398        CSSAgent.setPropertyText(this.id, index, name + ": " + value + ";", false, callback.bind(null, userCallback));
399    },
400
401    appendProperty: function(name, value, userCallback)
402    {
403        this.insertPropertyAt(this.allProperties.length, name, value, userCallback);
404    }
405}
406
407WebInspector.CSSRule = function(payload)
408{
409    this.id = payload.ruleId;
410    this.selectorText = payload.selectorText;
411    this.sourceLine = payload.sourceLine;
412    this.sourceURL = payload.sourceURL;
413    this.origin = payload.origin;
414    this.style = WebInspector.CSSStyleDeclaration.parsePayload(payload.style);
415    this.style.parentRule = this;
416    this.selectorRange = payload.selectorRange;
417}
418
419WebInspector.CSSRule.parsePayload = function(payload)
420{
421    return new WebInspector.CSSRule(payload);
422}
423
424WebInspector.CSSRule.prototype = {
425    get isUserAgent()
426    {
427        return this.origin === "user-agent";
428    },
429
430    get isUser()
431    {
432        return this.origin === "user";
433    },
434
435    get isViaInspector()
436    {
437        return this.origin === "inspector";
438    },
439
440    get isRegular()
441    {
442        return this.origin === "";
443    }
444}
445
446WebInspector.CSSProperty = function(ownerStyle, index, name, value, priority, status, parsedOk, implicit, shorthand, text)
447{
448    this.ownerStyle = ownerStyle;
449    this.index = index;
450    this.name = name;
451    this.value = value;
452    this.priority = priority;
453    this.status = status;
454    this.parsedOk = parsedOk;
455    this.implicit = implicit;
456    this.shorthand = shorthand;
457    this.text = text;
458}
459
460WebInspector.CSSProperty.parsePayload = function(ownerStyle, index, payload)
461{
462    // The following default field values are used in the payload:
463    // priority: ""
464    // parsedOk: true
465    // implicit: false
466    // status: "style"
467    // shorthandName: ""
468    var result = new WebInspector.CSSProperty(
469        ownerStyle, index, payload.name, payload.value, payload.priority || "", payload.status || "style", ("parsedOk" in payload) ? payload.parsedOk : true, !!payload.implicit, payload.shorthandName || "", payload.text);
470    return result;
471}
472
473WebInspector.CSSProperty.prototype = {
474    get propertyText()
475    {
476        if (this.text !== undefined)
477            return this.text;
478
479        if (this.name === "")
480            return "";
481        return this.name + ": " + this.value + (this.priority ? " !" + this.priority : "") + ";";
482    },
483
484    get isLive()
485    {
486        return this.active || this.styleBased;
487    },
488
489    get active()
490    {
491        return this.status === "active";
492    },
493
494    get styleBased()
495    {
496        return this.status === "style";
497    },
498
499    get inactive()
500    {
501        return this.status === "inactive";
502    },
503
504    get disabled()
505    {
506        return this.status === "disabled";
507    },
508
509    // Replaces "propertyName: propertyValue [!important];" in the stylesheet by an arbitrary propertyText.
510    setText: function(propertyText, majorChange, userCallback)
511    {
512        function enabledCallback(style)
513        {
514            if (style)
515                WebInspector.cssModel._fireStyleSheetChanged(style.id.styleSheetId, majorChange);
516            if (userCallback)
517                userCallback(style);
518        }
519
520        function callback(error, stylePayload)
521        {
522            if (!error) {
523                this.text = propertyText;
524                var style = WebInspector.CSSStyleDeclaration.parsePayload(stylePayload);
525                var newProperty = style.allProperties[this.index];
526
527                if (newProperty && this.disabled && !propertyText.match(/^\s*$/)) {
528                    newProperty.setDisabled(false, enabledCallback);
529                    return;
530                }
531
532                WebInspector.cssModel._fireStyleSheetChanged(style.id.styleSheetId, majorChange, userCallback.bind(this, style));
533            } else {
534                console.error(JSON.stringify(error));
535                if (userCallback)
536                    userCallback(null);
537            }
538        }
539
540        if (!this.ownerStyle)
541            throw "No ownerStyle for property";
542
543        // An index past all the properties adds a new property to the style.
544        CSSAgent.setPropertyText(this.ownerStyle.id, this.index, propertyText, this.index < this.ownerStyle.pastLastSourcePropertyIndex(), callback.bind(this));
545    },
546
547    setValue: function(newValue, userCallback)
548    {
549        var text = this.name + ": " + newValue + (this.priority ? " !" + this.priority : "") + ";"
550        this.setText(text, userCallback);
551    },
552
553    setDisabled: function(disabled, userCallback)
554    {
555        if (!this.ownerStyle && userCallback)
556            userCallback(null);
557        if (disabled === this.disabled && userCallback)
558            userCallback(this.ownerStyle);
559
560        function callback(error, stylePayload)
561        {
562            if (!userCallback)
563                return;
564            if (error)
565                userCallback(null);
566            else {
567                var style = WebInspector.CSSStyleDeclaration.parsePayload(stylePayload);
568                userCallback(style);
569                WebInspector.cssModel._fireStyleSheetChanged(this.ownerStyle.id.styleSheetId, false);
570            }
571        }
572
573        CSSAgent.toggleProperty(this.ownerStyle.id, this.index, disabled, callback.bind(this));
574    }
575}
576
577WebInspector.CSSStyleSheet = function(payload)
578{
579    this.id = payload.styleSheetId;
580    this.rules = [];
581    this.styles = {};
582    for (var i = 0; i < payload.rules.length; ++i) {
583        var rule = WebInspector.CSSRule.parsePayload(payload.rules[i]);
584        this.rules.push(rule);
585        if (rule.style)
586            this.styles[rule.style.id] = rule.style;
587    }
588    if ("text" in payload)
589        this._text = payload.text;
590}
591
592WebInspector.CSSStyleSheet.createForId = function(styleSheetId, userCallback)
593{
594    function callback(error, styleSheetPayload)
595    {
596        if (error)
597            userCallback(null);
598        else
599            userCallback(new WebInspector.CSSStyleSheet(styleSheetPayload));
600    }
601    CSSAgent.getStyleSheet(styleSheetId, callback.bind(this));
602}
603
604WebInspector.CSSStyleSheet.prototype = {
605    getText: function()
606    {
607        return this._text;
608    },
609
610    setText: function(newText, majorChange, userCallback)
611    {
612        function callback(error)
613        {
614             if (userCallback)
615                 userCallback(error);
616             if (!error)
617                 WebInspector.cssModel._fireStyleSheetChanged(this.id, majorChange);
618        }
619
620        CSSAgent.setStyleSheetText(this.id, newText, callback.bind(this));
621    }
622}
623
624WebInspector.CSSStyleModelResourceBinding = function(cssModel)
625{
626    this._cssModel = cssModel;
627    this._urlToStyleSheetId = {};
628    this._styleSheetIdToURL = {};
629    this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetChanged, this);
630    WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this);
631    WebInspector.Resource.registerDomainModelBinding(WebInspector.Resource.Type.Stylesheet, this);
632}
633
634WebInspector.CSSStyleModelResourceBinding.prototype = {
635    setContent: function(resource, content, majorChange, userCallback)
636    {
637        if (this._urlToStyleSheetId[resource.url]) {
638            this._innerSetContent(resource.url, content, majorChange, userCallback);
639            return;
640        }
641        this._loadStyleSheetHeaders(this._innerSetContent.bind(this, resource.url, content, majorChange, userCallback));
642    },
643
644    _frameNavigated: function(event)
645    {
646        var frameId = event.data;
647        if (!frameId) {
648            // Main frame navigation - clear history.
649            this._urlToStyleSheetId = {};
650            this._styleSheetIdToURL = {};
651        }
652    },
653
654    _innerSetContent: function(url, content, majorChange, userCallback, error)
655    {
656        if (error) {
657            userCallback(error);
658            return;
659        }
660
661        var styleSheetId = this._urlToStyleSheetId[url];
662        if (!styleSheetId) {
663            if (userCallback)
664                userCallback("No stylesheet found: " + url);
665            return;
666        }
667        this._cssModel.setStyleSheetText(styleSheetId, content, majorChange, userCallback);
668    },
669
670    _loadStyleSheetHeaders: function(callback)
671    {
672        function didGetAllStyleSheets(error, infos)
673        {
674            if (error) {
675                callback(error);
676                return;
677            }
678
679            for (var i = 0; i < infos.length; ++i) {
680                var info = infos[i];
681                this._urlToStyleSheetId[info.sourceURL] = info.styleSheetId;
682                this._styleSheetIdToURL[info.styleSheetId] = info.sourceURL;
683            }
684            callback();
685        }
686        CSSAgent.getAllStyleSheets(didGetAllStyleSheets.bind(this));
687    },
688
689    _styleSheetChanged: function(event)
690    {
691        var styleSheetId = event.data.styleSheetId;
692        function setContent()
693        {
694            var url = this._styleSheetIdToURL[styleSheetId];
695            if (!url)
696                return;
697
698            var resource = WebInspector.resourceForURL(url);
699            if (!resource)
700                return;
701
702            var majorChange = event.data.majorChange;
703            if (majorChange)
704                resource.addRevision(event.data.content);
705        }
706
707        if (!this._styleSheetIdToURL[styleSheetId]) {
708            this._loadStyleSheetHeaders(setContent.bind(this));
709            return;
710        }
711        setContent.call(this);
712    }
713}
714
715WebInspector.CSSStyleModelResourceBinding.prototype.__proto__ = WebInspector.ResourceDomainModelBinding.prototype;
716