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.RulesStyleDetailsPanel = function() 27{ 28 WebInspector.StyleDetailsPanel.call(this, WebInspector.RulesStyleDetailsPanel.StyleClassName, "rules", WebInspector.UIString("Rules")); 29 30 this._sections = []; 31}; 32 33WebInspector.RulesStyleDetailsPanel.StyleClassName = "rules"; 34WebInspector.RulesStyleDetailsPanel.LabelElementStyleClassName = "label"; 35WebInspector.RulesStyleDetailsPanel.NewRuleElementStyleClassName = "new-rule"; 36 37WebInspector.RulesStyleDetailsPanel.prototype = { 38 constructor: WebInspector.RulesStyleDetailsPanel, 39 40 // Public 41 42 refresh: function(significantChange) 43 { 44 // We only need to do a rebuild on significant changes. Other changes are handled 45 // by the sections and text editors themselves. 46 if (!significantChange) 47 return; 48 49 var newSections = []; 50 var newDOMFragment = document.createDocumentFragment(); 51 52 var previousMediaList = []; 53 var previousSection = null; 54 var previousFocusedSection = null; 55 56 function mediaListsEqual(a, b) 57 { 58 a = a || []; 59 b = b || []; 60 61 if (a.length !== b.length) 62 return false; 63 64 for (var i = 0; i < a.length; ++i) { 65 var aMedia = a[i]; 66 var bMedia = b[i]; 67 68 if (aMedia.type !== bMedia.type) 69 return false; 70 71 if (aMedia.text !== bMedia.text) 72 return false; 73 74 if (!aMedia.sourceCodeLocation && bMedia.sourceCodeLocation) 75 return false; 76 77 if (aMedia.sourceCodeLocation && !aMedia.sourceCodeLocation.isEqual(bMedia.sourceCodeLocation)) 78 return false; 79 } 80 81 return true; 82 } 83 84 function filteredMediaList(mediaList) 85 { 86 if (!mediaList) 87 return []; 88 89 // Exclude the basic "screen" query since it's very common and just clutters things. 90 return mediaList.filter(function(media) { 91 return media.text !== "screen"; 92 }); 93 } 94 95 function appendStyleSection(style) 96 { 97 var section = style.__rulesSection; 98 if (section && section.focused && !previousFocusedSection) 99 previousFocusedSection = section; 100 101 if (!section) { 102 section = new WebInspector.CSSStyleDeclarationSection(style); 103 style.__rulesSection = section; 104 } else 105 section.refresh(); 106 107 if (this._focusNextNewInspectorRule && style.ownerRule && style.ownerRule.type === WebInspector.CSSRule.Type.Inspector) { 108 previousFocusedSection = section; 109 delete this._focusNextNewInspectorRule; 110 } 111 112 // Reset lastInGroup in case the order/grouping changed. 113 section.lastInGroup = false; 114 115 newDOMFragment.appendChild(section.element); 116 newSections.push(section); 117 118 previousSection = section; 119 } 120 121 function addNewRuleButton() 122 { 123 if (previousSection) 124 previousSection.lastInGroup = true; 125 126 var newRuleButton = document.createElement("div"); 127 newRuleButton.className = WebInspector.RulesStyleDetailsPanel.NewRuleElementStyleClassName; 128 newRuleButton.addEventListener("click", this._newRuleClicked.bind(this)); 129 130 newRuleButton.appendChild(document.createElement("img")); 131 newRuleButton.appendChild(document.createTextNode(WebInspector.UIString("New Rule"))); 132 133 newDOMFragment.appendChild(newRuleButton); 134 135 addedNewRuleButton = true; 136 } 137 138 var pseudoElements = this.nodeStyles.pseudoElements; 139 for (var pseudoIdentifier in pseudoElements) { 140 var pseudoElement = pseudoElements[pseudoIdentifier]; 141 for (var i = 0; i < pseudoElement.orderedStyles.length; ++i) { 142 var style = pseudoElement.orderedStyles[i]; 143 appendStyleSection.call(this, style); 144 } 145 146 if (previousSection) 147 previousSection.lastInGroup = true; 148 } 149 150 var addedNewRuleButton = false; 151 152 var orderedStyles = this.nodeStyles.orderedStyles; 153 for (var i = 0; i < orderedStyles.length; ++i) { 154 var style = orderedStyles[i]; 155 156 if (style.type === WebInspector.CSSStyleDeclaration.Type.Rule && !addedNewRuleButton) 157 addNewRuleButton.call(this); 158 159 if (previousSection && previousSection.style.node !== style.node) { 160 previousSection.lastInGroup = true; 161 162 var prefixElement = document.createElement("strong"); 163 prefixElement.textContent = WebInspector.UIString("Inherited From: "); 164 165 var inheritedLabel = document.createElement("div"); 166 inheritedLabel.className = WebInspector.RulesStyleDetailsPanel.LabelElementStyleClassName; 167 inheritedLabel.appendChild(prefixElement); 168 inheritedLabel.appendChild(WebInspector.linkifyNodeReference(style.node)); 169 newDOMFragment.appendChild(inheritedLabel); 170 } 171 172 // Only include the media list if it is different from the previous media list shown. 173 var currentMediaList = filteredMediaList(style.ownerRule && style.ownerRule.mediaList); 174 if (!mediaListsEqual(previousMediaList, currentMediaList)) { 175 previousMediaList = currentMediaList; 176 177 // Break the section group even if the media list is empty. That way the user knows 178 // the previous displayed media list does not apply to the next section. 179 if (previousSection) 180 previousSection.lastInGroup = true; 181 182 for (var j = 0; j < currentMediaList.length; ++j) { 183 var media = currentMediaList[j]; 184 185 var prefixElement = document.createElement("strong"); 186 prefixElement.textContent = WebInspector.UIString("Media: "); 187 188 var mediaLabel = document.createElement("div"); 189 mediaLabel.className = WebInspector.RulesStyleDetailsPanel.LabelElementStyleClassName; 190 mediaLabel.appendChild(prefixElement); 191 mediaLabel.appendChild(document.createTextNode(media.text)); 192 193 if (media.sourceCodeLocation) { 194 mediaLabel.appendChild(document.createTextNode(" \u2014 ")); 195 mediaLabel.appendChild(WebInspector.createSourceCodeLocationLink(media.sourceCodeLocation, true)); 196 } 197 198 newDOMFragment.appendChild(mediaLabel); 199 } 200 } 201 202 appendStyleSection.call(this, style); 203 } 204 205 if (!addedNewRuleButton) 206 addNewRuleButton.call(this); 207 208 if (previousSection) 209 previousSection.lastInGroup = true; 210 211 this.element.removeChildren(); 212 this.element.appendChild(newDOMFragment); 213 214 this._sections = newSections; 215 216 for (var i = 0; i < this._sections.length; ++i) 217 this._sections[i].updateLayout(); 218 219 if (previousFocusedSection) { 220 previousFocusedSection.focus(); 221 222 function scrollToFocusedSection() 223 { 224 previousFocusedSection.element.scrollIntoViewIfNeeded(true); 225 } 226 227 // Do the scroll on a timeout since StyleDetailsPanel restores scroll position 228 // after the refresh, and we might not need to scroll after the restore. 229 setTimeout(scrollToFocusedSection, 0); 230 } 231 }, 232 233 // Protected 234 235 shown: function() 236 { 237 WebInspector.StyleDetailsPanel.prototype.shown.call(this); 238 239 // Associate the style and section objects so they can be reused. 240 // Also update the layout in case we changed widths while hidden. 241 for (var i = 0; i < this._sections.length; ++i) { 242 var section = this._sections[i]; 243 section.style.__rulesSection = section; 244 section.updateLayout(); 245 } 246 }, 247 248 hidden: function() 249 { 250 WebInspector.StyleDetailsPanel.prototype.hidden.call(this); 251 252 // Disconnect the style and section objects so they have a chance 253 // to release their objects when this panel is not visible. 254 for (var i = 0; i < this._sections.length; ++i) 255 delete this._sections[i].style.__rulesSection; 256 }, 257 258 widthDidChange: function() 259 { 260 for (var i = 0; i < this._sections.length; ++i) 261 this._sections[i].updateLayout(); 262 }, 263 264 // Private 265 266 _newRuleClicked: function(event) 267 { 268 this._focusNextNewInspectorRule = true; 269 this.nodeStyles.addRule(); 270 } 271}; 272 273WebInspector.RulesStyleDetailsPanel.prototype.__proto__ = WebInspector.StyleDetailsPanel.prototype; 274