1/*
2 * Copyright (C) 2013 Apple 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
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26WebInspector.StyleDetailsPanel = function(className, identifier, label)
27{
28    this._element = document.createElement("div");
29    this._element.className = className;
30
31    // Add this offset-sections class name so the sticky headers don't overlap the navigation bar.
32    this.element.classList.add(WebInspector.StyleDetailsPanel.OffsetSectionsStyleClassName);
33
34    this._navigationItem = new WebInspector.RadioButtonNavigationItem(identifier, label);
35
36    this._nodeStyles = null;
37    this._visible = false;
38};
39
40WebInspector.StyleDetailsPanel.OffsetSectionsStyleClassName = "offset-sections";
41
42WebInspector.StyleDetailsPanel.prototype = {
43    constructor: WebInspector.StyleDetailsPanel,
44
45    // Public
46
47    get element()
48    {
49        return this._element;
50    },
51
52    get navigationItem()
53    {
54        return this._navigationItem;
55    },
56
57    get nodeStyles()
58    {
59        return this._nodeStyles;
60    },
61
62    shown: function()
63    {
64        if (this._visible)
65            return;
66
67        this._visible = true;
68
69        this._refreshNodeStyles();
70    },
71
72    hidden: function()
73    {
74        this._visible = false;
75    },
76
77    widthDidChange: function()
78    {
79        // Implemented by subclasses.
80    },
81
82    markAsNeedsRefresh: function(domNode)
83    {
84        console.assert(domNode);
85        if (!domNode)
86            return;
87
88        if (!this._nodeStyles || this._nodeStyles.node !== domNode) {
89            if (this._nodeStyles) {
90                this._nodeStyles.removeEventListener(WebInspector.DOMNodeStyles.Event.Refreshed, this._nodeStylesRefreshed, this);
91                this._nodeStyles.removeEventListener(WebInspector.DOMNodeStyles.Event.NeedsRefresh, this._nodeStylesNeedsRefreshed, this);
92            }
93
94            this._nodeStyles = WebInspector.cssStyleManager.stylesForNode(domNode);
95
96            console.assert(this._nodeStyles);
97            if (!this._nodeStyles)
98                return;
99
100            this._nodeStyles.addEventListener(WebInspector.DOMNodeStyles.Event.Refreshed, this._nodeStylesRefreshed, this);
101            this._nodeStyles.addEventListener(WebInspector.DOMNodeStyles.Event.NeedsRefresh, this._nodeStylesNeedsRefreshed, this);
102
103            this._forceSignificantChange = true;
104        }
105
106        if (this._visible)
107            this._refreshNodeStyles();
108    },
109
110    refresh: function(significantChange)
111    {
112        // Implemented by subclasses.
113    },
114
115    // Private
116
117    get _initialScrollOffset()
118    {
119        if (!WebInspector.cssStyleManager.canForcePseudoClasses())
120            return 0;
121        return this.nodeStyles.node.enabledPseudoClasses.length ? 0 : WebInspector.CSSStyleDetailsSidebarPanel.NoForcedPseudoClassesScrollOffset;
122    },
123
124    _refreshNodeStyles: function()
125    {
126        if (!this._nodeStyles)
127            return;
128        this._nodeStyles.refresh();
129    },
130
131    _refreshPreservingScrollPosition: function(significantChange)
132    {
133        significantChange = this._forceSignificantChange || significantChange || false;
134        delete this._forceSignificantChange;
135
136        var previousScrollTop = this._initialScrollOffset;
137
138        // Only remember the scroll position if the previous node is the same as this one.
139        if (this.element.parentNode && this._previousRefreshNodeIdentifier === this._nodeStyles.node.id)
140            previousScrollTop = this.element.parentNode.scrollTop;
141
142        this.refresh(significantChange);
143
144        this._previousRefreshNodeIdentifier = this._nodeStyles.node.id;
145
146        if (this.element.parentNode)
147            this.element.parentNode.scrollTop = previousScrollTop;
148    },
149
150    _nodeStylesRefreshed: function(event)
151    {
152        if (this._visible)
153            this._refreshPreservingScrollPosition(event.data.significantChange);
154    },
155
156    _nodeStylesNeedsRefreshed: function(event)
157    {
158        if (this._visible)
159            this._refreshNodeStyles();
160    }
161};
162
163WebInspector.StyleDetailsPanel.prototype.__proto__ = WebInspector.Object.prototype;
164