UNPKG

strong-arc

Version:

A visual suite for the StrongLoop API Platform

1,334 lines (1,153 loc) 128 kB
/* * Copyright (C) 2007 Apple Inc. All rights reserved. * Copyright (C) 2009 Joseph Pecoraro * * 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. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. */ /** * @constructor * @extends {WebInspector.SidebarPane} * @param {!WebInspector.ComputedStyleSidebarPane} computedStylePane * @param {function(!WebInspector.DOMNode, string, boolean)=} setPseudoClassCallback */ WebInspector.StylesSidebarPane = function(computedStylePane, setPseudoClassCallback) { WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles")); this._elementStateButton = document.createElement("button"); this._elementStateButton.className = "pane-title-button element-state"; this._elementStateButton.title = WebInspector.UIString("Toggle Element State"); this._elementStateButton.addEventListener("click", this._toggleElementStatePane.bind(this), false); this.titleElement.appendChild(this._elementStateButton); var addButton = document.createElement("button"); addButton.className = "pane-title-button add"; addButton.id = "add-style-button-test-id"; addButton.title = WebInspector.UIString("New Style Rule"); addButton.addEventListener("click", this._createNewRuleInViaInspectorStyleSheet.bind(this), false); this.titleElement.appendChild(addButton); this._computedStylePane = computedStylePane; computedStylePane.setHostingPane(this); this._setPseudoClassCallback = setPseudoClassCallback; this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true); WebInspector.settings.colorFormat.addChangeListener(this._colorFormatSettingChanged.bind(this)); WebInspector.settings.showUserAgentStyles.addChangeListener(this._showUserAgentStylesSettingChanged.bind(this)); this._createElementStatePane(); this.bodyElement.appendChild(this._elementStatePane); this._sectionsContainer = document.createElement("div"); this.bodyElement.appendChild(this._sectionsContainer); this._spectrumHelper = new WebInspector.SpectrumPopupHelper(); this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultCSSFormatter()); this.element.classList.add("styles-pane"); this.element.classList.toggle("show-user-styles", WebInspector.settings.showUserAgentStyles.get()); this.element.addEventListener("mousemove", this._mouseMovedOverElement.bind(this), false); document.body.addEventListener("keydown", this._keyDown.bind(this), false); document.body.addEventListener("keyup", this._keyUp.bind(this), false); } // Keep in sync with RenderStyleConstants.h PseudoId enum. Array below contains pseudo id names for corresponding enum indexes. // First item is empty due to its artificial NOPSEUDO nature in the enum. // FIXME: find a way of generating this mapping or getting it from combination of RenderStyleConstants and CSSSelector.cpp at // runtime. WebInspector.StylesSidebarPane.PseudoIdNames = [ "", "first-line", "first-letter", "before", "after", "backdrop", "selection", "", "-webkit-scrollbar", "-webkit-scrollbar-thumb", "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece", "-webkit-scrollbar-corner", "-webkit-resizer" ]; WebInspector.StylesSidebarPane._colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b(?!-))/g; /** * @enum {string} */ WebInspector.StylesSidebarPane.Events = { SelectorEditingStarted: "SelectorEditingStarted", SelectorEditingEnded: "SelectorEditingEnded" }; /** * @param {!WebInspector.CSSProperty} property * @return {!Element} */ WebInspector.StylesSidebarPane.createExclamationMark = function(property) { var exclamationElement = document.createElement("div"); exclamationElement.className = "exclamation-mark" + (WebInspector.StylesSidebarPane._ignoreErrorsForProperty(property) ? "" : " warning-icon-small"); exclamationElement.title = WebInspector.CSSMetadata.cssPropertiesMetainfo.keySet()[property.name.toLowerCase()] ? WebInspector.UIString("Invalid property value.") : WebInspector.UIString("Unknown property name."); return exclamationElement; } /** * @param {!WebInspector.Color} color */ WebInspector.StylesSidebarPane._colorFormat = function(color) { const cf = WebInspector.Color.Format; var format; var formatSetting = WebInspector.settings.colorFormat.get(); if (formatSetting === cf.Original) format = cf.Original; else if (formatSetting === cf.RGB) format = (color.hasAlpha() ? cf.RGBA : cf.RGB); else if (formatSetting === cf.HSL) format = (color.hasAlpha() ? cf.HSLA : cf.HSL); else if (!color.hasAlpha()) format = (color.canBeShortHex() ? cf.ShortHEX : cf.HEX); else format = cf.RGBA; return format; } /** * @param {!WebInspector.CSSProperty} property */ WebInspector.StylesSidebarPane._ignoreErrorsForProperty = function(property) { function hasUnknownVendorPrefix(string) { return !string.startsWith("-webkit-") && /^[-_][\w\d]+-\w/.test(string); } var name = property.name.toLowerCase(); // IE hack. if (name.charAt(0) === "_") return true; // IE has a different format for this. if (name === "filter") return true; // Common IE-specific property prefix. if (name.startsWith("scrollbar-")) return true; if (hasUnknownVendorPrefix(name)) return true; var value = property.value.toLowerCase(); // IE hack. if (value.endsWith("\9")) return true; if (hasUnknownVendorPrefix(value)) return true; return false; } WebInspector.StylesSidebarPane.prototype = { /** * @param {!WebInspector.DOMNode} node */ updateEditingSelectorForNode: function(node) { var selectorText = WebInspector.DOMPresentationUtils.simpleSelector(node); if (!selectorText) return; this._editingSelectorSection.setSelectorText(selectorText); }, /** * @return {boolean} */ isEditingSelector: function() { return !!this._editingSelectorSection; }, /** * @param {!WebInspector.StylePropertiesSection} section */ _startEditingSelector: function(section) { this._editingSelectorSection = section; this.dispatchEventToListeners(WebInspector.StylesSidebarPane.Events.SelectorEditingStarted); }, _finishEditingSelector: function() { delete this._editingSelectorSection; this.dispatchEventToListeners(WebInspector.StylesSidebarPane.Events.SelectorEditingEnded); }, /** * @param {!WebInspector.CSSRule} editedRule * @param {!WebInspector.TextRange} oldRange * @param {!WebInspector.TextRange} newRange */ _styleSheetRuleEdited: function(editedRule, oldRange, newRange) { var styleRuleSections = this.sections[0]; for (var i = 1; i < styleRuleSections.length; ++i) styleRuleSections[i]._styleSheetRuleEdited(editedRule, oldRange, newRange); }, /** * @param {!Event} event */ _contextMenuEventFired: function(event) { // We start editing upon click -> default navigation to resources panel is not available // Hence we add a soft context menu for hrefs. var contextMenu = new WebInspector.ContextMenu(event); contextMenu.appendApplicableItems(/** @type {!Node} */ (event.target)); contextMenu.show(); }, /** * @param {!Element} matchedStylesElement * @param {!Element} computedStylesElement */ setFilterBoxContainers: function(matchedStylesElement, computedStylesElement) { matchedStylesElement.appendChild(this._createCSSFilterControl()); this._computedStylePane.setFilterBoxContainer(computedStylesElement); }, /** * @return {!Element} */ _createCSSFilterControl: function() { var filterInput = this._createPropertyFilterElement(false, searchHandler.bind(this)); /** * @param {?RegExp} regex * @this {WebInspector.StylesSidebarPane} */ function searchHandler(regex) { this._filterRegex = regex; } return filterInput; }, get _forcedPseudoClasses() { return this._node ? (this._node.getUserProperty(WebInspector.CSSStyleModel.PseudoStatePropertyName) || undefined) : undefined; }, _updateForcedPseudoStateInputs: function() { if (!this._node) return; var hasPseudoType = !!this._node.pseudoType(); this._elementStateButton.classList.toggle("hidden", hasPseudoType); this._elementStatePane.classList.toggle("expanded", !hasPseudoType && this._elementStateButton.classList.contains("toggled")); var nodePseudoState = this._forcedPseudoClasses; if (!nodePseudoState) nodePseudoState = []; var inputs = this._elementStatePane.inputs; for (var i = 0; i < inputs.length; ++i) inputs[i].checked = nodePseudoState.indexOf(inputs[i].state) >= 0; }, /** * @param {?WebInspector.DOMNode} node * @param {boolean=} forceUpdate */ update: function(node, forceUpdate) { this._spectrumHelper.hide(); this._discardElementUnderMouse(); var refresh = false; if (forceUpdate) delete this._node; if (!forceUpdate && (node === this._node)) refresh = true; if (node && node.nodeType() === Node.TEXT_NODE && node.parentNode) node = node.parentNode; if (node && node.nodeType() !== Node.ELEMENT_NODE) node = null; if (node) { this._updateTarget(node.target()); this._node = node; } else node = this._node; this._updateForcedPseudoStateInputs(); if (refresh) this._refreshUpdate(); else this._rebuildUpdate(); }, /** * @param {!WebInspector.Target} target */ _updateTarget: function(target) { if (this._target === target) return; if (this._target) { this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetOrMediaQueryResultChanged, this); this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetOrMediaQueryResultChanged, this); this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this); this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this); this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeChanged, this); this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeChanged, this); this._target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this); } this._target = target; this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetOrMediaQueryResultChanged, this); this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetOrMediaQueryResultChanged, this); this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this); this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this); this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeChanged, this); this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeChanged, this); this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this); }, /** * @param {!WebInspector.StylePropertiesSection=} editedSection * @param {boolean=} forceFetchComputedStyle * @param {function()=} userCallback */ _refreshUpdate: function(editedSection, forceFetchComputedStyle, userCallback) { var callbackWrapper = function() { if (this._filterRegex) this._updateFilter(false); if (userCallback) userCallback(); }.bind(this); if (this._refreshUpdateInProgress) { this._lastNodeForInnerRefresh = this._node; return; } var node = this._validateNode(userCallback); if (!node) return; /** * @param {?WebInspector.CSSStyleDeclaration} computedStyle * @this {WebInspector.StylesSidebarPane} */ function computedStyleCallback(computedStyle) { delete this._refreshUpdateInProgress; if (this._lastNodeForInnerRefresh) { delete this._lastNodeForInnerRefresh; this._refreshUpdate(editedSection, forceFetchComputedStyle, callbackWrapper); return; } if (this._node === node && computedStyle) this._innerRefreshUpdate(node, computedStyle, editedSection); callbackWrapper(); } if (this._computedStylePane.isShowing() || forceFetchComputedStyle) { this._refreshUpdateInProgress = true; this._target.cssModel.getComputedStyleAsync(node.id, computedStyleCallback.bind(this)); } else { this._innerRefreshUpdate(node, null, editedSection); callbackWrapper(); } }, _rebuildUpdate: function() { if (this._rebuildUpdateInProgress) { this._lastNodeForInnerRebuild = this._node; return; } var node = this._validateNode(); if (!node) return; this._rebuildUpdateInProgress = true; var resultStyles = {}; /** * @param {?*} matchedResult * @this {WebInspector.StylesSidebarPane} */ function stylesCallback(matchedResult) { delete this._rebuildUpdateInProgress; var lastNodeForRebuild = this._lastNodeForInnerRebuild; if (lastNodeForRebuild) { delete this._lastNodeForInnerRebuild; if (lastNodeForRebuild !== this._node) { this._rebuildUpdate(); return; } } if (matchedResult && this._node === node) { resultStyles.matchedCSSRules = matchedResult.matchedCSSRules; resultStyles.pseudoElements = matchedResult.pseudoElements; resultStyles.inherited = matchedResult.inherited; this._innerRebuildUpdate(node, resultStyles); } if (lastNodeForRebuild) { // lastNodeForRebuild is the same as this.node - another rebuild has been requested. this._rebuildUpdate(); return; } } /** * @param {?WebInspector.CSSStyleDeclaration} inlineStyle * @param {?WebInspector.CSSStyleDeclaration} attributesStyle */ function inlineCallback(inlineStyle, attributesStyle) { resultStyles.inlineStyle = inlineStyle; resultStyles.attributesStyle = attributesStyle; } /** * @param {?WebInspector.CSSStyleDeclaration} computedStyle */ function computedCallback(computedStyle) { resultStyles.computedStyle = computedStyle; } if (this._computedStylePane.isShowing()) this._target.cssModel.getComputedStyleAsync(node.id, computedCallback); this._target.cssModel.getInlineStylesAsync(node.id, inlineCallback); this._target.cssModel.getMatchedStylesAsync(node.id, false, false, stylesCallback.bind(this)); }, /** * @param {function()=} userCallback */ _validateNode: function(userCallback) { if (!this._node) { this._sectionsContainer.removeChildren(); this._computedStylePane.bodyElement.removeChildren(); this.sections = {}; if (userCallback) userCallback(); return null; } return this._node; }, _styleSheetOrMediaQueryResultChanged: function() { if (this._userOperation || this._isEditingStyle) return; this._rebuildUpdate(); }, _frameResized: function() { /** * @this {WebInspector.StylesSidebarPane} */ function refreshContents() { this._styleSheetOrMediaQueryResultChanged(); delete this._activeTimer; } if (this._activeTimer) clearTimeout(this._activeTimer); this._activeTimer = setTimeout(refreshContents.bind(this), 100); }, _attributeChanged: function(event) { // Any attribute removal or modification can affect the styles of "related" nodes. // Do not touch the styles if they are being edited. if (this._isEditingStyle || this._userOperation) return; if (!this._canAffectCurrentStyles(event.data.node)) return; this._rebuildUpdate(); }, _canAffectCurrentStyles: function(node) { return this._node && (this._node === node || node.parentNode === this._node.parentNode || node.isAncestor(this._node)); }, _innerRefreshUpdate: function(node, computedStyle, editedSection) { for (var pseudoId in this.sections) { var styleRules = this._refreshStyleRules(this.sections[pseudoId], computedStyle); var usedProperties = {}; this._markUsedProperties(styleRules, usedProperties); this._refreshSectionsForStyleRules(styleRules, usedProperties, editedSection); } if (computedStyle) this.sections[0][0].rebuildComputedTrace(this.sections[0]); this._nodeStylesUpdatedForTest(node, false); }, _innerRebuildUpdate: function(node, styles) { this._sectionsContainer.removeChildren(); this._computedStylePane.bodyElement.removeChildren(); this._linkifier.reset(); var styleRules = this._rebuildStyleRules(node, styles); var usedProperties = {}; this._markUsedProperties(styleRules, usedProperties); this.sections[0] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, null); var anchorElement = this.sections[0].inheritedPropertiesSeparatorElement; if (styles.computedStyle) this.sections[0][0].rebuildComputedTrace(this.sections[0]); for (var i = 0; i < styles.pseudoElements.length; ++i) { var pseudoElementCSSRules = styles.pseudoElements[i]; styleRules = []; var pseudoId = pseudoElementCSSRules.pseudoId; var entry = { isStyleSeparator: true, pseudoId: pseudoId }; styleRules.push(entry); // Add rules in reverse order to match the cascade order. for (var j = pseudoElementCSSRules.rules.length - 1; j >= 0; --j) { var rule = pseudoElementCSSRules.rules[j]; styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, rule: rule, editable: !!(rule.style && rule.style.styleSheetId) }); } usedProperties = {}; this._markUsedProperties(styleRules, usedProperties); this.sections[pseudoId] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, anchorElement); } if (this._filterRegex) this._updateFilter(false); this._nodeStylesUpdatedForTest(node, true); }, _nodeStylesUpdatedForTest: function(node, rebuild) { // Tests override this method. }, _refreshStyleRules: function(sections, computedStyle) { var nodeComputedStyle = computedStyle; var styleRules = []; for (var i = 0; sections && i < sections.length; ++i) { var section = sections[i]; if (section.isBlank) continue; if (section.computedStyle) section.styleRule.style = nodeComputedStyle; var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule, editable: !!(section.styleRule.style && section.styleRule.style.styleSheetId), isAttribute: section.styleRule.isAttribute, isInherited: section.styleRule.isInherited, parentNode: section.styleRule.parentNode }; styleRules.push(styleRule); } return styleRules; }, _rebuildStyleRules: function(node, styles) { var nodeComputedStyle = styles.computedStyle; this.sections = {}; var styleRules = []; function addAttributesStyle() { if (!styles.attributesStyle) return; var attrStyle = { style: styles.attributesStyle, editable: false }; attrStyle.selectorText = node.nodeNameInCorrectCase() + "[" + WebInspector.UIString("Attributes Style") + "]"; styleRules.push(attrStyle); } styleRules.push({ computedStyle: true, selectorText: "", style: nodeComputedStyle, editable: false }); if (!!node.pseudoType()) styleRules.push({ isStyleSeparator: true, isPlaceholder: true }); // Inline style has the greatest specificity. if (styles.inlineStyle && node.nodeType() === Node.ELEMENT_NODE) { var inlineStyle = { selectorText: "element.style", style: styles.inlineStyle, isAttribute: true }; styleRules.push(inlineStyle); } // Add rules in reverse order to match the cascade order. var addedAttributesStyle; for (var i = styles.matchedCSSRules.length - 1; i >= 0; --i) { var rule = styles.matchedCSSRules[i]; if ((rule.isUser || rule.isUserAgent) && !addedAttributesStyle) { // Show element's Style Attributes after all author rules. addedAttributesStyle = true; addAttributesStyle(); } styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, rule: rule, editable: !!(rule.style && rule.style.styleSheetId) }); } if (!addedAttributesStyle) addAttributesStyle(); // Walk the node structure and identify styles with inherited properties. var parentNode = node.parentNode; function insertInheritedNodeSeparator(node) { var entry = {}; entry.isStyleSeparator = true; entry.node = node; styleRules.push(entry); } for (var parentOrdinal = 0; parentOrdinal < styles.inherited.length; ++parentOrdinal) { var parentStyles = styles.inherited[parentOrdinal]; var separatorInserted = false; if (parentStyles.inlineStyle) { if (this._containsInherited(parentStyles.inlineStyle)) { var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: parentStyles.inlineStyle, isAttribute: true, isInherited: true, parentNode: parentNode }; if (!separatorInserted) { insertInheritedNodeSeparator(parentNode); separatorInserted = true; } styleRules.push(inlineStyle); } } for (var i = parentStyles.matchedCSSRules.length - 1; i >= 0; --i) { var rulePayload = parentStyles.matchedCSSRules[i]; if (!this._containsInherited(rulePayload.style)) continue; var rule = rulePayload; if (!separatorInserted) { insertInheritedNodeSeparator(parentNode); separatorInserted = true; } styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, rule: rule, isInherited: true, parentNode: parentNode, editable: !!(rule.style && rule.style.styleSheetId) }); } parentNode = parentNode.parentNode; } return styleRules; }, _markUsedProperties: function(styleRules, usedProperties) { var foundImportantProperties = {}; var propertyToEffectiveRule = {}; var inheritedPropertyToNode = {}; for (var i = 0; i < styleRules.length; ++i) { var styleRule = styleRules[i]; if (styleRule.computedStyle || styleRule.isStyleSeparator) continue; if (styleRule.section && styleRule.section.noAffect) continue; styleRule.usedProperties = {}; var style = styleRule.style; var allProperties = style.allProperties; for (var j = 0; j < allProperties.length; ++j) { var property = allProperties[j]; if (!property.isLive || !property.parsedOk) continue; // Do not pick non-inherited properties from inherited styles. if (styleRule.isInherited && !WebInspector.CSSMetadata.isPropertyInherited(property.name)) continue; var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(property.name); if (foundImportantProperties.hasOwnProperty(canonicalName)) continue; if (!property.important && usedProperties.hasOwnProperty(canonicalName)) continue; var isKnownProperty = propertyToEffectiveRule.hasOwnProperty(canonicalName); if (!isKnownProperty && styleRule.isInherited && !inheritedPropertyToNode[canonicalName]) inheritedPropertyToNode[canonicalName] = styleRule.parentNode; if (property.important) { if (styleRule.isInherited && isKnownProperty && styleRule.parentNode !== inheritedPropertyToNode[canonicalName]) continue; foundImportantProperties[canonicalName] = true; if (isKnownProperty) delete propertyToEffectiveRule[canonicalName].usedProperties[canonicalName]; } styleRule.usedProperties[canonicalName] = true; usedProperties[canonicalName] = true; propertyToEffectiveRule[canonicalName] = styleRule; } } }, _refreshSectionsForStyleRules: function(styleRules, usedProperties, editedSection) { // Walk the style rules and update the sections with new overloaded and used properties. for (var i = 0; i < styleRules.length; ++i) { var styleRule = styleRules[i]; var section = styleRule.section; if (styleRule.computedStyle) { section._usedProperties = usedProperties; section.update(); } else { section._usedProperties = styleRule.usedProperties; section.update(section === editedSection); } } }, /** * @param {!Array.<!Object>} styleRules * @param {!Object.<string, boolean>} usedProperties * @param {?Element} anchorElement */ _rebuildSectionsForStyleRules: function(styleRules, usedProperties, anchorElement) { // Make a property section for each style rule. var sections = []; for (var i = 0; i < styleRules.length; ++i) { var styleRule = styleRules[i]; if (styleRule.isStyleSeparator) { var separatorElement = document.createElement("div"); if (styleRule.isPlaceholder) { separatorElement.className = "styles-sidebar-placeholder"; this._sectionsContainer.insertBefore(separatorElement, anchorElement); continue; } separatorElement.className = "sidebar-separator"; if (styleRule.node) { var link = WebInspector.DOMPresentationUtils.linkifyNodeReference(styleRule.node); separatorElement.appendChild(document.createTextNode(WebInspector.UIString("Inherited from") + " ")); separatorElement.appendChild(link); if (!sections.inheritedPropertiesSeparatorElement) sections.inheritedPropertiesSeparatorElement = separatorElement; } else if ("pseudoId" in styleRule) { var pseudoName = WebInspector.StylesSidebarPane.PseudoIdNames[styleRule.pseudoId]; if (pseudoName) separatorElement.textContent = WebInspector.UIString("Pseudo ::%s element", pseudoName); else separatorElement.textContent = WebInspector.UIString("Pseudo element"); } else separatorElement.textContent = styleRule.text; this._sectionsContainer.insertBefore(separatorElement, anchorElement); continue; } var computedStyle = styleRule.computedStyle; // Default editable to true if it was omitted. var editable = styleRule.editable; if (typeof editable === "undefined") editable = true; if (computedStyle) var section = new WebInspector.ComputedStylePropertiesSection(this, styleRule, usedProperties); else { var section = new WebInspector.StylePropertiesSection(this, styleRule, editable, styleRule.isInherited); section._markSelectorMatches(); } section.expanded = true; if (computedStyle) this._computedStylePane.bodyElement.appendChild(section.element); else this._sectionsContainer.insertBefore(section.element, anchorElement); sections.push(section); } return sections; }, _containsInherited: function(style) { var properties = style.allProperties; for (var i = 0; i < properties.length; ++i) { var property = properties[i]; // Does this style contain non-overridden inherited property? if (property.isLive && WebInspector.CSSMetadata.isPropertyInherited(property.name)) return true; } return false; }, _colorFormatSettingChanged: function(event) { for (var pseudoId in this.sections) { var sections = this.sections[pseudoId]; for (var i = 0; i < sections.length; ++i) sections[i].update(true); } }, /** * @param {?Event} event */ _createNewRuleInViaInspectorStyleSheet: function(event) { event.consume(); var cssModel = this._target.cssModel; cssModel.requestViaInspectorStylesheet(this._node, viaInspectorCallback.bind(this)); /** * @param {?WebInspector.CSSStyleSheetHeader} styleSheetHeader * @this {WebInspector.StylesSidebarPane} */ function viaInspectorCallback(styleSheetHeader) { if (!styleSheetHeader) return; styleSheetHeader.requestContent(onViaInspectorContent.bind(this, styleSheetHeader.id)); } /** * @param {string} styleSheetId * @param {string} text * @this {WebInspector.StylesSidebarPane} */ function onViaInspectorContent(styleSheetId, text) { var lines = text.split("\n"); var range = WebInspector.TextRange.createFromLocation(lines.length - 1, lines[lines.length - 1].length); this._addBlankSection(this.sections[0][1], styleSheetId, range); } }, /** * @param {!WebInspector.StylePropertiesSection} insertAfterSection * @param {string} styleSheetId * @param {!WebInspector.TextRange} ruleLocation */ _addBlankSection: function(insertAfterSection, styleSheetId, ruleLocation) { this.expand(); var blankSection = new WebInspector.BlankStylePropertiesSection(this, this._node ? WebInspector.DOMPresentationUtils.simpleSelector(this._node) : "", styleSheetId, ruleLocation, insertAfterSection.rule); this._sectionsContainer.insertBefore(blankSection.element, insertAfterSection.element.nextSibling); var index = this.sections[0].indexOf(insertAfterSection); this.sections[0].splice(index + 1, 0, blankSection); blankSection.startEditingSelector(); }, removeSection: function(section) { for (var pseudoId in this.sections) { var sections = this.sections[pseudoId]; var index = sections.indexOf(section); if (index === -1) continue; sections.splice(index, 1); section.element.remove(); } }, _toggleElementStatePane: function(event) { event.consume(); var buttonToggled = !this._elementStateButton.classList.contains("toggled"); if (buttonToggled) this.expand(); this._elementStateButton.classList.toggle("toggled", buttonToggled); this._elementStatePane.classList.toggle("expanded", buttonToggled); }, _createElementStatePane: function() { this._elementStatePane = document.createElement("div"); this._elementStatePane.className = "styles-element-state-pane source-code"; var table = document.createElement("table"); var inputs = []; this._elementStatePane.inputs = inputs; /** * @param {!Event} event * @this {WebInspector.StylesSidebarPane} */ function clickListener(event) { var node = this._validateNode(); if (!node) return; this._setPseudoClassCallback(node, event.target.state, event.target.checked); } /** * @param {string} state * @return {!Element} * @this {WebInspector.StylesSidebarPane} */ function createCheckbox(state) { var td = document.createElement("td"); var label = document.createElement("label"); var input = document.createElement("input"); input.type = "checkbox"; input.state = state; input.addEventListener("click", clickListener.bind(this), false); inputs.push(input); label.appendChild(input); label.appendChild(document.createTextNode(":" + state)); td.appendChild(label); return td; } var tr = table.createChild("tr"); tr.appendChild(createCheckbox.call(this, "active")); tr.appendChild(createCheckbox.call(this, "hover")); tr = table.createChild("tr"); tr.appendChild(createCheckbox.call(this, "focus")); tr.appendChild(createCheckbox.call(this, "visited")); this._elementStatePane.appendChild(table); }, /** * @return {?RegExp} */ filterRegex: function() { return this._filterRegex; }, /** * @param {boolean} isComputedStyleFilter * @return {!Element} * @param {function(?RegExp)} filterCallback */ _createPropertyFilterElement: function(isComputedStyleFilter, filterCallback) { var input = document.createElement("input"); input.type = "text"; input.placeholder = isComputedStyleFilter ? WebInspector.UIString("Filter") : WebInspector.UIString("Find in Styles"); var boundSearchHandler = searchHandler.bind(this); /** * @this {WebInspector.StylesSidebarPane} */ function searchHandler() { var regex = input.value ? new RegExp(input.value.escapeForRegExp(), "i") : null; filterCallback(regex); input.parentNode.classList.toggle("styles-filter-engaged", !!input.value); this._updateFilter(isComputedStyleFilter); } input.addEventListener("input", boundSearchHandler, false); /** * @param {!Event} event */ function keydownHandler(event) { var Esc = "U+001B"; if (event.keyIdentifier !== Esc || !input.value) return; event.consume(true); input.value = ""; boundSearchHandler(); } input.addEventListener("keydown", keydownHandler, false); return input; }, /** * @param {boolean} isComputedStyleFilter */ _updateFilter: function(isComputedStyleFilter) { for (var pseudoId in this.sections) { var sections = this.sections[pseudoId]; for (var i = 0; i < sections.length; ++i) { var section = sections[i]; if (isComputedStyleFilter !== !!section.computedStyle) continue; section._updateFilter(); } } }, /** * @param {!WebInspector.Event} event */ _showUserAgentStylesSettingChanged: function(event) { var showStyles = /** @type {boolean} */ (event.data); this.element.classList.toggle("show-user-styles", showStyles); }, willHide: function() { this._spectrumHelper.hide(); this._discardElementUnderMouse(); }, _discardElementUnderMouse: function() { if (this._elementUnderMouse) this._elementUnderMouse.classList.remove("styles-panel-hovered"); delete this._elementUnderMouse; }, _mouseMovedOverElement: function(e) { if (this._elementUnderMouse && e.target !== this._elementUnderMouse) this._discardElementUnderMouse(); this._elementUnderMouse = e.target; if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(e)) this._elementUnderMouse.classList.add("styles-panel-hovered"); }, _keyDown: function(e) { if ((!WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Ctrl.code) || (WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Meta.code)) { if (this._elementUnderMouse) this._elementUnderMouse.classList.add("styles-panel-hovered"); } }, _keyUp: function(e) { if ((!WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Ctrl.code) || (WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Meta.code)) { this._discardElementUnderMouse(); } }, __proto__: WebInspector.SidebarPane.prototype } /** * @constructor * @extends {WebInspector.SidebarPane} */ WebInspector.ComputedStyleSidebarPane = function() { WebInspector.SidebarPane.call(this, WebInspector.UIString("Computed Style")); } WebInspector.ComputedStyleSidebarPane.prototype = { /** * @param {!WebInspector.StylesSidebarPane} pane */ setHostingPane: function(pane) { this._stylesSidebarPane = pane; }, setFilterBoxContainer: function(element) { element.appendChild(this._stylesSidebarPane._createPropertyFilterElement(true, filterCallback.bind(this))); /** * @param {?RegExp} regex * @this {WebInspector.ComputedStyleSidebarPane} */ function filterCallback(regex) { this._filterRegex = regex; } }, wasShown: function() { WebInspector.SidebarPane.prototype.wasShown.call(this); if (!this._hasFreshContent) this.prepareContent(); }, /** * @param {function()=} callback */ prepareContent: function(callback) { /** * @this {WebInspector.ComputedStyleSidebarPane} */ function wrappedCallback() { this._hasFreshContent = true; if (callback) callback(); delete this._hasFreshContent; } this._stylesSidebarPane._refreshUpdate(null, true, wrappedCallback.bind(this)); }, /** * @return {?RegExp} */ filterRegex: function() { return this._filterRegex; }, __proto__: WebInspector.SidebarPane.prototype } /** * @constructor * @extends {WebInspector.PropertiesSection} * @param {!WebInspector.StylesSidebarPane} parentPane * @param {!Object} styleRule * @param {boolean} editable * @param {boolean} isInherited */ WebInspector.StylePropertiesSection = function(parentPane, styleRule, editable, isInherited) { WebInspector.PropertiesSection.call(this, ""); this._parentPane = parentPane; this.styleRule = styleRule; this.rule = this.styleRule.rule; this.editable = editable; this.isInherited = isInherited; var extraClasses = (this.rule && (this.rule.isUser || this.rule.isUserAgent) ? " user-rule" : ""); this.element.className = "styles-section matched-styles monospace" + extraClasses; // We don't really use properties' disclosure. this.propertiesElement.classList.remove("properties-tree"); var selectorContainer = document.createElement("div"); this._selectorElement = document.createElement("span"); this._selectorElement.textContent = styleRule.selectorText; selectorContainer.appendChild(this._selectorElement); var openBrace = document.createElement("span"); openBrace.textContent = " {"; selectorContainer.appendChild(openBrace); selectorContainer.addEventListener("mousedown", this._handleEmptySpaceMouseDown.bind(this), false); selectorContainer.addEventListener("click", this._handleSelectorContainerClick.bind(this), false); var closeBrace = document.createElement("div"); closeBrace.textContent = "}"; this.element.appendChild(closeBrace); if (this.editable && this.rule) { var newRuleButton = closeBrace.createChild("div", "sidebar-pane-button-new-rule"); newRuleButton.title = WebInspector.UIString("Insert Style Rule"); newRuleButton.addEventListener("click", this._onNewRuleClick.bind(this), false); } this._selectorElement.addEventListener("click", this._handleSelectorClick.bind(this), false); this.element.addEventListener("mousedown", this._handleEmptySpaceMouseDown.bind(this), false); this.element.addEventListener("click", this._handleEmptySpaceClick.bind(this), false); if (this.rule) { // Prevent editing the user agent and user rules. if (this.rule.isUserAgent || this.rule.isUser) this.editable = false; else { // Check this is a real CSSRule, not a bogus object coming from WebInspector.BlankStylePropertiesSection. if (this.rule.styleSheetId) this.navigable = !!this.rule.resourceURL(); } this.titleElement.classList.add("styles-selector"); } this._usedProperties = styleRule.usedProperties; this._selectorRefElement = document.createElement("div"); this._selectorRefElement.className = "subtitle"; this._mediaListElement = this.titleElement.createChild("div", "media-list"); this._updateMediaList(); this._updateRuleOrigin(); selectorContainer.insertBefore(this._selectorRefElement, selectorContainer.firstChild); this.titleElement.appendChild(selectorContainer); this._selectorContainer = selectorContainer; if (isInherited) this.element.classList.add("styles-show-inherited"); // This one is related to inherited rules, not computed style. if (this.navigable) this.element.classList.add("navigable"); if (!this.editable) this.element.classList.add("read-only"); } WebInspector.StylePropertiesSection.prototype = { /** * @param {?Event} event */ _onNewRuleClick: function(event) { event.consume(); var range = WebInspector.TextRange.createFromLocation(this.rule.style.range.endLine, this.rule.style.range.endColumn + 1); this._parentPane._addBlankSection(this, this.rule.styleSheetId, range); }, /** * @param {!WebInspector.CSSRule} editedRule * @param {!WebInspector.TextRange} oldRange * @param {!WebInspector.TextRange} newRange */ _styleSheetRuleEdited: function(editedRule, oldRange, newRange) { if (!this.rule || !this.rule.styleSheetId) return; if (this.rule !== editedRule) this.rule.sourceStyleSheetEdited(editedRule.styleSheetId, oldRange, newRange); this._updateMediaList(); this._updateRuleOrigin(); }, /** * @param {!Object} styleRule */ _createMediaList: function(styleRule) { if (!styleRule.media) return; for (var i = styleRule.media.length - 1; i >= 0; --i) { var media = styleRule.media[i]; var mediaDataElement = this._mediaListElement.createChild("div", "media"); var mediaText; switch (media.source) { case WebInspector.CSSMedia.Source.LINKED_SHEET: case WebInspector.CSSMedia.Source.INLINE_SHEET: mediaText = "media=\"" + media.text + "\""; break; case WebInspector.CSSMedia.Source.MEDIA_RULE: mediaText = "@media " + media.text; break; case WebInspector.CSSMedia.Source.IMPORT_RULE: mediaText = "@import " + media.text; break; } if (media.sourceURL) { var refElement = mediaDataElement.createChild("div", "subtitle"); var anchor = this._parentPane._linkifier.linkifyMedia(media); anchor.style.float = "right"; refElement.appendChild(anchor); } var mediaTextElement = mediaDataElement.createChild("span"); mediaTextElement.textContent = mediaText; mediaTextElement.title = media.text; } }, _updateMediaList: function() { this._mediaListElement.removeChildren(); this._createMediaList(this.styleRule); }, collapse: function() { // Overriding with empty body. }, handleClick: function() { // Avoid consuming events. }, /** * @param {string} propertyName * @return {boolean} */ isPropertyInherited: function(propertyName) { if (this.isInherited) { // While rendering inherited stylesheet, reverse meaning of this property. // Render truly inherited properties with black, i.e. return them as non-inherited. return !WebInspector.CSSMetadata.isPropertyInherited(propertyName); } return false; }, /** * @param {string} propertyName * @param {boolean=} isShorthand * @return {boolean} */ isPropertyOverloaded: function(propertyName, isShorthand) { if (!this._usedProperties || this.noAffect) return false; if (this.isInherited && !WebInspector.CSSMetadata.isPropertyInherited(propertyName)) { // In the inherited sections, only show overrides for the potentially inherited properties. return false; } var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(propertyName); var used = (canonicalName in this._usedProperties); if (used || !isShorthand) return !used; // Find out if any of the individual longhand properties of the shorthand // are used, if none are then the shorthand is overloaded too. var longhandProperties = this.styleRule.style.longhandProperties(propertyName); for (var j = 0; j < longhandProperties.length; ++j) {