/* * Copyright (C) 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ WebInspector.VisualStyleSelectorSection = class VisualStyleSelectorSection extends WebInspector.DetailsSection { constructor(delegate) { let selectorSection = {element: document.createElement("div")}; selectorSection.element.classList.add("selectors"); let controlElement = document.createElement("div"); controlElement.classList.add("controls"); super("visual-style-selector-section", WebInspector.UIString("Style Rules"), [selectorSection], controlElement); this._delegate = delegate || null; this._nodeStyles = null; this._currentSelectorElement = document.createElement("div"); this._currentSelectorElement.classList.add("current-selector"); let currentSelectorIconElement = document.createElement("img"); currentSelectorIconElement.classList.add("icon"); this._currentSelectorElement.appendChild(currentSelectorIconElement); this._currentSelectorText = document.createElement("span"); this._currentSelectorElement.appendChild(this._currentSelectorText); this._headerElement.appendChild(this._currentSelectorElement); let selectorListElement = document.createElement("ol"); selectorListElement.classList.add("selector-list"); selectorSection.element.appendChild(selectorListElement); this._selectors = new WebInspector.TreeOutline(selectorListElement); this._selectors.disclosureButtons = false; this._selectors.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._selectorChanged, this); this._newInspectorRuleSelector = null; let addGlyphElement = useSVGSymbol("Images/Plus13.svg", "visual-style-selector-section-add-rule", WebInspector.UIString("Click to add a new rule.")); addGlyphElement.addEventListener("click", this._addNewRule.bind(this)); controlElement.appendChild(addGlyphElement); this._headerElement.addEventListener("mouseover", this._handleMouseOver.bind(this)); this._headerElement.addEventListener("mouseout", this._handleMouseOut.bind(this)); } // Public update(nodeStyles) { let style = this.currentStyle(); if (style) this._nodeStyles[WebInspector.VisualStyleSelectorSection.LastSelectedRuleSymbol] = style; if (nodeStyles) this._nodeStyles = nodeStyles; if (!this._nodeStyles) return; this._selectors.removeChildren(); let previousRule = null; // Pseudo Styles let pseudoRules = []; let pseudoElements = this._nodeStyles.pseudoElements; for (let pseudoIdentifier in pseudoElements) pseudoRules = pseudoRules.concat(pseudoElements[pseudoIdentifier].matchedRules); let orderedPseudoRules = uniqueOrderedRules(pseudoRules); // Reverse the array to ensure that splicing the array will not mess with the order. if (orderedPseudoRules.length) orderedPseudoRules.reverse(); function createSelectorItem(style, title, subtitle) { let selector = new WebInspector.VisualStyleSelectorTreeItem(this, style, title, subtitle); selector.addEventListener(WebInspector.VisualStyleSelectorTreeItem.Event.CheckboxChanged, this._treeElementCheckboxToggled, this); this._selectors.appendChild(selector); if (style.isInspectorRule() && this._newInspectorRuleSelector === style.selectorText && !style.hasProperties()) { selector.select(true); selector.element.scrollIntoView(); this._nodeStyles[WebInspector.VisualStyleSelectorSection.LastSelectedRuleSymbol] = style; this._newInspectorRuleSelector = null; return; } if (this._nodeStyles[WebInspector.VisualStyleSelectorSection.LastSelectedRuleSymbol] === style) { selector.select(true); selector.element.scrollIntoView(); } } function uniqueOrderedRules(orderedRules) { if (!orderedRules || !orderedRules.length) return new Array; let uniqueRules = new Map; for (let rule of orderedRules) { if (!uniqueRules.has(rule.id)) uniqueRules.set(rule.id, rule); } return Array.from(uniqueRules.values()); } function insertAllMatchingPseudoRules(force) { if (!orderedPseudoRules.length) return; if (force) { for (let i = orderedPseudoRules.length - 1; i >= 0; --i) { let pseudoRule = orderedPseudoRules[i]; createSelectorItem.call(this, pseudoRule.style, pseudoRule.selectorText, pseudoRule.mediaText); } orderedPseudoRules = []; } if (!previousRule) return; for (let i = orderedPseudoRules.length - 1; i >= 0; --i) { let pseudoRule = orderedPseudoRules[i]; if (!pseudoRule.selectorIsGreater(previousRule.mostSpecificSelector)) continue; createSelectorItem.call(this, pseudoRule.style, pseudoRule.selectorText, pseudoRule.mediaText); previousRule = pseudoRule; orderedPseudoRules.splice(i, 1); } } if (this._nodeStyles.inlineStyle) { if (!this._nodeStyles[WebInspector.VisualStyleSelectorSection.LastSelectedRuleSymbol]) this._nodeStyles[WebInspector.VisualStyleSelectorSection.LastSelectedRuleSymbol] = this._nodeStyles.inlineStyle; // Inline Style createSelectorItem.call(this, this._nodeStyles.inlineStyle, WebInspector.UIString("This Element")); } else if (!this._nodeStyles[WebInspector.VisualStyleSelectorSection.LastSelectedRuleSymbol]) this._nodeStyles[WebInspector.VisualStyleSelectorSection.LastSelectedRuleSymbol] = this._nodeStyles.matchedRules[0].style; // Matched Rules for (let rule of uniqueOrderedRules(this._nodeStyles.matchedRules)) { if (rule.type === WebInspector.CSSStyleSheet.Type.UserAgent) { insertAllMatchingPseudoRules.call(this, true); continue; } insertAllMatchingPseudoRules.call(this); createSelectorItem.call(this, rule.style, rule.selectorText, rule.mediaText); previousRule = rule; } // Just in case there are any remaining pseudo-styles. insertAllMatchingPseudoRules.call(this, true); // Inherited Rules for (let inherited of this._nodeStyles.inheritedRules) { if (!inherited.matchedRules || !inherited.matchedRules.length) continue; let divider = null; for (let rule of uniqueOrderedRules(inherited.matchedRules)) { if (rule.type === WebInspector.CSSStyleSheet.Type.UserAgent) continue; if (!divider) { let dividerText = WebInspector.UIString("Inherited from %s").format(WebInspector.displayNameForNode(inherited.node)); divider = new WebInspector.GeneralTreeElement("section-divider", dividerText); divider.selectable = false; this._selectors.appendChild(divider); } createSelectorItem.call(this, rule.style, rule.selectorText, rule.mediaText); } } this._newInspectorRuleSelector = null; } currentStyle() { if (!this._nodeStyles || !this._selectors.selectedTreeElement) return; return this._selectors.selectedTreeElement.representedObject; } treeItemForStyle(style) { for (let item of this._selectors.children) { if (item.representedObject === style) return item; } return null; } selectEmptyStyleTreeItem(style) { if (style.hasProperties()) return false; let treeItem = this.treeItemForStyle(style); if (!treeItem) return false; treeItem.select(true, true); return true; } // Private _selectorChanged(event) { let selectedTreeElement = event.data.selectedElement; if (!selectedTreeElement) return; // The class needs to be completely reset as the previously selected treeElement most likely had // a different icon className and it is simpler to regenerate the class than to find out which // class was previously applied. this._currentSelectorElement.className = "current-selector " + selectedTreeElement.iconClassName; let selectorText = selectedTreeElement.mainTitle; let mediaText = selectedTreeElement.subtitle; if (mediaText && mediaText.length) selectorText += " \u2014 " + mediaText; // em-dash this._currentSelectorText.textContent = selectorText; this.dispatchEventToListeners(WebInspector.VisualStyleSelectorSection.Event.SelectorChanged); } _addNewRule(event) { if (!this._nodeStyles || this._nodeStyles.node.isInShadowTree()) return; let selector = this.currentStyle().selectorText; let existingRules = this._nodeStyles.rulesForSelector(selector); for (let rule of existingRules) { if (this.selectEmptyStyleTreeItem(rule.style)) return; } this._newInspectorRuleSelector = selector; this._nodeStyles.addRule(selector); } _treeElementCheckboxToggled(event) { let style = this.currentStyle(); if (!style) return; let styleText = style.text; if (!styleText || !styleText.length) return; // Comment or uncomment the style text. let newStyleText = ""; let styleEnabled = event && event.data && event.data.enabled; if (styleEnabled) newStyleText = styleText.replace(/\s*(\/\*|\*\/)\s*/g, ""); else newStyleText = "/* " + styleText.replace(/(\s*;(?!$)\s*)/g, "$1 *//* ") + " */"; style.text = newStyleText; style[WebInspector.VisualStyleDetailsPanel.StyleDisabledSymbol] = !styleEnabled; this.dispatchEventToListeners(WebInspector.VisualStyleSelectorSection.Event.SelectorChanged); } _handleMouseOver() { if (!this.collapsed) return; let style = this.currentStyle(); if (!style) return; if (!style.ownerRule) { WebInspector.domTreeManager.highlightDOMNode(style.node.id); return; } WebInspector.domTreeManager.highlightSelector(style.ownerRule.selectorText, style.node.ownerDocument.frameIdentifier); } _handleMouseOut() { if (!this.collapsed) return; WebInspector.domTreeManager.hideDOMNodeHighlight(); } }; WebInspector.VisualStyleSelectorSection.LastSelectedRuleSymbol = Symbol("visual-style-selector-section-last-selected-rule"); WebInspector.VisualStyleSelectorSection.Event = { SelectorChanged: "visual-style-selector-section-selector-changed" }