2 * Copyright (C) 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2009 Joseph Pecoraro
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 * @extends {WebInspector.SidebarPane}
33 * @param {!WebInspector.ComputedStyleSidebarPane} computedStylePane
34 * @param {function(!WebInspector.DOMNode, string, boolean)=} setPseudoClassCallback
36 WebInspector.StylesSidebarPane = function(computedStylePane, setPseudoClassCallback)
38 WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles"));
40 this._elementStateButton = document.createElement("button");
41 this._elementStateButton.className = "pane-title-button element-state";
42 this._elementStateButton.title = WebInspector.UIString("Toggle Element State");
43 this._elementStateButton.addEventListener("click", this._toggleElementStatePane.bind(this), false);
44 this.titleElement.appendChild(this._elementStateButton);
46 var addButton = document.createElement("button");
47 addButton.className = "pane-title-button add";
48 addButton.id = "add-style-button-test-id";
49 addButton.title = WebInspector.UIString("New Style Rule");
50 addButton.addEventListener("click", this._createNewRuleInViaInspectorStyleSheet.bind(this), false);
51 this.titleElement.appendChild(addButton);
53 this._computedStylePane = computedStylePane;
54 computedStylePane.setHostingPane(this);
55 this._setPseudoClassCallback = setPseudoClassCallback;
56 this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
57 WebInspector.settings.colorFormat.addChangeListener(this._colorFormatSettingChanged.bind(this));
58 WebInspector.settings.showUserAgentStyles.addChangeListener(this._showUserAgentStylesSettingChanged.bind(this));
60 this._createElementStatePane();
61 this.bodyElement.appendChild(this._elementStatePane);
62 this._sectionsContainer = document.createElement("div");
63 this.bodyElement.appendChild(this._sectionsContainer);
65 this._spectrumHelper = new WebInspector.SpectrumPopupHelper();
66 this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultCSSFormatter());
68 this.element.classList.add("styles-pane");
69 this.element.classList.toggle("show-user-styles", WebInspector.settings.showUserAgentStyles.get());
70 this.element.addEventListener("mousemove", this._mouseMovedOverElement.bind(this), false);
71 document.body.addEventListener("keydown", this._keyDown.bind(this), false);
72 document.body.addEventListener("keyup", this._keyUp.bind(this), false);
75 // Keep in sync with RenderStyleConstants.h PseudoId enum. Array below contains pseudo id names for corresponding enum indexes.
76 // First item is empty due to its artificial NOPSEUDO nature in the enum.
77 // FIXME: find a way of generating this mapping or getting it from combination of RenderStyleConstants and CSSSelector.cpp at
79 WebInspector.StylesSidebarPane.PseudoIdNames = [
80 "", "first-line", "first-letter", "before", "after", "backdrop", "selection", "", "-webkit-scrollbar",
81 "-webkit-scrollbar-thumb", "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece",
82 "-webkit-scrollbar-corner", "-webkit-resizer"
85 WebInspector.StylesSidebarPane._colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b(?!-))/g;
88 * @param {!WebInspector.CSSProperty} property
91 WebInspector.StylesSidebarPane.createExclamationMark = function(property)
93 var exclamationElement = document.createElement("div");
94 exclamationElement.className = "exclamation-mark" + (WebInspector.StylesSidebarPane._ignoreErrorsForProperty(property) ? "" : " warning-icon-small");
95 exclamationElement.title = WebInspector.CSSMetadata.cssPropertiesMetainfo.keySet()[property.name.toLowerCase()] ? WebInspector.UIString("Invalid property value.") : WebInspector.UIString("Unknown property name.");
96 return exclamationElement;
100 * @param {!WebInspector.Color} color
102 WebInspector.StylesSidebarPane._colorFormat = function(color)
104 const cf = WebInspector.Color.Format;
106 var formatSetting = WebInspector.settings.colorFormat.get();
107 if (formatSetting === cf.Original)
108 format = cf.Original;
109 else if (formatSetting === cf.RGB)
110 format = (color.hasAlpha() ? cf.RGBA : cf.RGB);
111 else if (formatSetting === cf.HSL)
112 format = (color.hasAlpha() ? cf.HSLA : cf.HSL);
113 else if (!color.hasAlpha())
114 format = (color.canBeShortHex() ? cf.ShortHEX : cf.HEX);
122 * @param {!WebInspector.CSSProperty} property
124 WebInspector.StylesSidebarPane._ignoreErrorsForProperty = function(property) {
125 function hasUnknownVendorPrefix(string)
127 return !string.startsWith("-webkit-") && /^[-_][\w\d]+-\w/.test(string);
130 var name = property.name.toLowerCase();
133 if (name.charAt(0) === "_")
136 // IE has a different format for this.
137 if (name === "filter")
140 // Common IE-specific property prefix.
141 if (name.startsWith("scrollbar-"))
143 if (hasUnknownVendorPrefix(name))
146 var value = property.value.toLowerCase();
149 if (value.endsWith("\9"))
151 if (hasUnknownVendorPrefix(value))
157 WebInspector.StylesSidebarPane.prototype = {
159 * @param {!WebInspector.CSSRule} editedRule
160 * @param {!WebInspector.TextRange} oldRange
161 * @param {!WebInspector.TextRange} newRange
163 _styleSheetRuleEdited: function(editedRule, oldRange, newRange)
165 var styleRuleSections = this.sections[0];
166 for (var i = 1; i < styleRuleSections.length; ++i)
167 styleRuleSections[i]._styleSheetRuleEdited(editedRule, oldRange, newRange);
171 * @param {!Event} event
173 _contextMenuEventFired: function(event)
175 // We start editing upon click -> default navigation to resources panel is not available
176 // Hence we add a soft context menu for hrefs.
177 var contextMenu = new WebInspector.ContextMenu(event);
178 contextMenu.appendApplicableItems(/** @type {!Node} */ (event.target));
183 * @param {!Element} matchedStylesElement
184 * @param {!Element} computedStylesElement
186 setFilterBoxContainers: function(matchedStylesElement, computedStylesElement)
188 matchedStylesElement.appendChild(this._createCSSFilterControl());
189 this._computedStylePane.setFilterBoxContainer(computedStylesElement);
195 _createCSSFilterControl: function()
197 var filterInput = this._createPropertyFilterElement(false, searchHandler.bind(this));
200 * @param {?RegExp} regex
201 * @this {WebInspector.StylesSidebarPane}
203 function searchHandler(regex)
205 this._filterRegex = regex;
211 get _forcedPseudoClasses()
213 return this._node ? (this._node.getUserProperty(WebInspector.CSSStyleModel.PseudoStatePropertyName) || undefined) : undefined;
216 _updateForcedPseudoStateInputs: function()
221 var hasPseudoType = !!this._node.pseudoType();
222 this._elementStateButton.classList.toggle("hidden", hasPseudoType);
223 this._elementStatePane.classList.toggle("expanded", !hasPseudoType && this._elementStateButton.classList.contains("toggled"));
225 var nodePseudoState = this._forcedPseudoClasses;
226 if (!nodePseudoState)
227 nodePseudoState = [];
229 var inputs = this._elementStatePane.inputs;
230 for (var i = 0; i < inputs.length; ++i)
231 inputs[i].checked = nodePseudoState.indexOf(inputs[i].state) >= 0;
235 * @param {?WebInspector.DOMNode} node
236 * @param {boolean=} forceUpdate
238 update: function(node, forceUpdate)
240 this._spectrumHelper.hide();
241 this._discardElementUnderMouse();
248 if (!forceUpdate && (node === this._node))
251 if (node && node.nodeType() === Node.TEXT_NODE && node.parentNode)
252 node = node.parentNode;
254 if (node && node.nodeType() !== Node.ELEMENT_NODE)
258 this._updateTarget(node.target());
263 this._updateForcedPseudoStateInputs();
266 this._refreshUpdate();
268 this._rebuildUpdate();
272 * @param {!WebInspector.Target} target
274 _updateTarget: function(target)
276 if (this._target === target)
279 this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetOrMediaQueryResultChanged, this);
280 this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetOrMediaQueryResultChanged, this);
281 this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
282 this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
283 this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeChanged, this);
284 this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeChanged, this);
285 this._target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
287 this._target = target;
288 this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetOrMediaQueryResultChanged, this);
289 this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetOrMediaQueryResultChanged, this);
290 this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
291 this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
292 this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeChanged, this);
293 this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeChanged, this);
294 this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
298 * @param {!WebInspector.StylePropertiesSection=} editedSection
299 * @param {boolean=} forceFetchComputedStyle
300 * @param {function()=} userCallback
302 _refreshUpdate: function(editedSection, forceFetchComputedStyle, userCallback)
304 var callbackWrapper = function()
306 if (this._filterRegex)
307 this._updateFilter(false);
312 if (this._refreshUpdateInProgress) {
313 this._lastNodeForInnerRefresh = this._node;
317 var node = this._validateNode(userCallback);
322 * @param {?WebInspector.CSSStyleDeclaration} computedStyle
323 * @this {WebInspector.StylesSidebarPane}
325 function computedStyleCallback(computedStyle)
327 delete this._refreshUpdateInProgress;
329 if (this._lastNodeForInnerRefresh) {
330 delete this._lastNodeForInnerRefresh;
331 this._refreshUpdate(editedSection, forceFetchComputedStyle, callbackWrapper);
335 if (this._node === node && computedStyle)
336 this._innerRefreshUpdate(node, computedStyle, editedSection);
341 if (this._computedStylePane.isShowing() || forceFetchComputedStyle) {
342 this._refreshUpdateInProgress = true;
343 this._target.cssModel.getComputedStyleAsync(node.id, computedStyleCallback.bind(this));
345 this._innerRefreshUpdate(node, null, editedSection);
350 _rebuildUpdate: function()
352 if (this._rebuildUpdateInProgress) {
353 this._lastNodeForInnerRebuild = this._node;
357 var node = this._validateNode();
361 this._rebuildUpdateInProgress = true;
363 var resultStyles = {};
366 * @param {?*} matchedResult
367 * @this {WebInspector.StylesSidebarPane}
369 function stylesCallback(matchedResult)
371 delete this._rebuildUpdateInProgress;
373 var lastNodeForRebuild = this._lastNodeForInnerRebuild;
374 if (lastNodeForRebuild) {
375 delete this._lastNodeForInnerRebuild;
376 if (lastNodeForRebuild !== this._node) {
377 this._rebuildUpdate();
382 if (matchedResult && this._node === node) {
383 resultStyles.matchedCSSRules = matchedResult.matchedCSSRules;
384 resultStyles.pseudoElements = matchedResult.pseudoElements;
385 resultStyles.inherited = matchedResult.inherited;
386 this._innerRebuildUpdate(node, resultStyles);
389 if (lastNodeForRebuild) {
390 // lastNodeForRebuild is the same as this.node - another rebuild has been requested.
391 this._rebuildUpdate();
397 * @param {?WebInspector.CSSStyleDeclaration} inlineStyle
398 * @param {?WebInspector.CSSStyleDeclaration} attributesStyle
400 function inlineCallback(inlineStyle, attributesStyle)
402 resultStyles.inlineStyle = inlineStyle;
403 resultStyles.attributesStyle = attributesStyle;
407 * @param {?WebInspector.CSSStyleDeclaration} computedStyle
409 function computedCallback(computedStyle)
411 resultStyles.computedStyle = computedStyle;
414 if (this._computedStylePane.isShowing())
415 this._target.cssModel.getComputedStyleAsync(node.id, computedCallback);
416 this._target.cssModel.getInlineStylesAsync(node.id, inlineCallback);
417 this._target.cssModel.getMatchedStylesAsync(node.id, false, false, stylesCallback.bind(this));
421 * @param {function()=} userCallback
423 _validateNode: function(userCallback)
426 this._sectionsContainer.removeChildren();
427 this._computedStylePane.bodyElement.removeChildren();
436 _styleSheetOrMediaQueryResultChanged: function()
438 if (this._userOperation || this._isEditingStyle)
441 this._rebuildUpdate();
444 _frameResized: function()
447 * @this {WebInspector.StylesSidebarPane}
449 function refreshContents()
451 this._styleSheetOrMediaQueryResultChanged();
452 delete this._activeTimer;
455 if (this._activeTimer)
456 clearTimeout(this._activeTimer);
458 this._activeTimer = setTimeout(refreshContents.bind(this), 100);
461 _attributeChanged: function(event)
463 // Any attribute removal or modification can affect the styles of "related" nodes.
464 // Do not touch the styles if they are being edited.
465 if (this._isEditingStyle || this._userOperation)
468 if (!this._canAffectCurrentStyles(event.data.node))
471 this._rebuildUpdate();
474 _canAffectCurrentStyles: function(node)
476 return this._node && (this._node === node || node.parentNode === this._node.parentNode || node.isAncestor(this._node));
479 _innerRefreshUpdate: function(node, computedStyle, editedSection)
481 for (var pseudoId in this.sections) {
482 var styleRules = this._refreshStyleRules(this.sections[pseudoId], computedStyle);
483 var usedProperties = {};
484 this._markUsedProperties(styleRules, usedProperties);
485 this._refreshSectionsForStyleRules(styleRules, usedProperties, editedSection);
488 this.sections[0][0].rebuildComputedTrace(this.sections[0]);
490 this._nodeStylesUpdatedForTest(node, false);
493 _innerRebuildUpdate: function(node, styles)
495 this._sectionsContainer.removeChildren();
496 this._computedStylePane.bodyElement.removeChildren();
497 this._linkifier.reset();
499 var styleRules = this._rebuildStyleRules(node, styles);
500 var usedProperties = {};
501 this._markUsedProperties(styleRules, usedProperties);
502 this.sections[0] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, null);
503 var anchorElement = this.sections[0].inheritedPropertiesSeparatorElement;
505 if (styles.computedStyle)
506 this.sections[0][0].rebuildComputedTrace(this.sections[0]);
508 for (var i = 0; i < styles.pseudoElements.length; ++i) {
509 var pseudoElementCSSRules = styles.pseudoElements[i];
512 var pseudoId = pseudoElementCSSRules.pseudoId;
514 var entry = { isStyleSeparator: true, pseudoId: pseudoId };
515 styleRules.push(entry);
517 // Add rules in reverse order to match the cascade order.
518 for (var j = pseudoElementCSSRules.rules.length - 1; j >= 0; --j) {
519 var rule = pseudoElementCSSRules.rules[j];
520 styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, rule: rule, editable: !!(rule.style && rule.style.styleSheetId) });
523 this._markUsedProperties(styleRules, usedProperties);
524 this.sections[pseudoId] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, anchorElement);
527 if (this._filterRegex)
528 this._updateFilter(false);
529 this._nodeStylesUpdatedForTest(node, true);
532 _nodeStylesUpdatedForTest: function(node, rebuild)
534 // Tests override this method.
537 _refreshStyleRules: function(sections, computedStyle)
539 var nodeComputedStyle = computedStyle;
541 for (var i = 0; sections && i < sections.length; ++i) {
542 var section = sections[i];
545 if (section.computedStyle)
546 section.styleRule.style = nodeComputedStyle;
547 var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule, editable: !!(section.styleRule.style && section.styleRule.style.styleSheetId),
548 isAttribute: section.styleRule.isAttribute, isInherited: section.styleRule.isInherited, parentNode: section.styleRule.parentNode };
549 styleRules.push(styleRule);
554 _rebuildStyleRules: function(node, styles)
556 var nodeComputedStyle = styles.computedStyle;
561 function addAttributesStyle()
563 if (!styles.attributesStyle)
565 var attrStyle = { style: styles.attributesStyle, editable: false };
566 attrStyle.selectorText = node.nodeNameInCorrectCase() + "[" + WebInspector.UIString("Attributes Style") + "]";
567 styleRules.push(attrStyle);
570 styleRules.push({ computedStyle: true, selectorText: "", style: nodeComputedStyle, editable: false });
572 if (!!node.pseudoType())
573 styleRules.push({ isStyleSeparator: true, isPlaceholder: true });
575 // Inline style has the greatest specificity.
576 if (styles.inlineStyle && node.nodeType() === Node.ELEMENT_NODE) {
577 var inlineStyle = { selectorText: "element.style", style: styles.inlineStyle, isAttribute: true };
578 styleRules.push(inlineStyle);
581 // Add rules in reverse order to match the cascade order.
582 var addedAttributesStyle;
583 for (var i = styles.matchedCSSRules.length - 1; i >= 0; --i) {
584 var rule = styles.matchedCSSRules[i];
585 if ((rule.isUser || rule.isUserAgent) && !addedAttributesStyle) {
586 // Show element's Style Attributes after all author rules.
587 addedAttributesStyle = true;
588 addAttributesStyle();
590 styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, rule: rule, editable: !!(rule.style && rule.style.styleSheetId) });
593 if (!addedAttributesStyle)
594 addAttributesStyle();
596 // Walk the node structure and identify styles with inherited properties.
597 var parentNode = node.parentNode;
598 function insertInheritedNodeSeparator(node)
601 entry.isStyleSeparator = true;
603 styleRules.push(entry);
606 for (var parentOrdinal = 0; parentOrdinal < styles.inherited.length; ++parentOrdinal) {
607 var parentStyles = styles.inherited[parentOrdinal];
608 var separatorInserted = false;
609 if (parentStyles.inlineStyle) {
610 if (this._containsInherited(parentStyles.inlineStyle)) {
611 var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: parentStyles.inlineStyle, isAttribute: true, isInherited: true, parentNode: parentNode };
612 if (!separatorInserted) {
613 insertInheritedNodeSeparator(parentNode);
614 separatorInserted = true;
616 styleRules.push(inlineStyle);
620 for (var i = parentStyles.matchedCSSRules.length - 1; i >= 0; --i) {
621 var rulePayload = parentStyles.matchedCSSRules[i];
622 if (!this._containsInherited(rulePayload.style))
624 var rule = rulePayload;
626 if (!separatorInserted) {
627 insertInheritedNodeSeparator(parentNode);
628 separatorInserted = true;
630 styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, rule: rule, isInherited: true, parentNode: parentNode, editable: !!(rule.style && rule.style.styleSheetId) });
632 parentNode = parentNode.parentNode;
637 _markUsedProperties: function(styleRules, usedProperties)
639 var foundImportantProperties = {};
640 var propertyToEffectiveRule = {};
641 var inheritedPropertyToNode = {};
642 for (var i = 0; i < styleRules.length; ++i) {
643 var styleRule = styleRules[i];
644 if (styleRule.computedStyle || styleRule.isStyleSeparator)
646 if (styleRule.section && styleRule.section.noAffect)
649 styleRule.usedProperties = {};
651 var style = styleRule.style;
652 var allProperties = style.allProperties;
653 for (var j = 0; j < allProperties.length; ++j) {
654 var property = allProperties[j];
655 if (!property.isLive || !property.parsedOk)
658 // Do not pick non-inherited properties from inherited styles.
659 if (styleRule.isInherited && !WebInspector.CSSMetadata.isPropertyInherited(property.name))
662 var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(property.name);
663 if (foundImportantProperties.hasOwnProperty(canonicalName))
666 if (!property.important && usedProperties.hasOwnProperty(canonicalName))
669 var isKnownProperty = propertyToEffectiveRule.hasOwnProperty(canonicalName);
670 if (!isKnownProperty && styleRule.isInherited && !inheritedPropertyToNode[canonicalName])
671 inheritedPropertyToNode[canonicalName] = styleRule.parentNode;
673 if (property.important) {
674 if (styleRule.isInherited && isKnownProperty && styleRule.parentNode !== inheritedPropertyToNode[canonicalName])
677 foundImportantProperties[canonicalName] = true;
679 delete propertyToEffectiveRule[canonicalName].usedProperties[canonicalName];
682 styleRule.usedProperties[canonicalName] = true;
683 usedProperties[canonicalName] = true;
684 propertyToEffectiveRule[canonicalName] = styleRule;
689 _refreshSectionsForStyleRules: function(styleRules, usedProperties, editedSection)
691 // Walk the style rules and update the sections with new overloaded and used properties.
692 for (var i = 0; i < styleRules.length; ++i) {
693 var styleRule = styleRules[i];
694 var section = styleRule.section;
695 if (styleRule.computedStyle) {
696 section._usedProperties = usedProperties;
699 section._usedProperties = styleRule.usedProperties;
700 section.update(section === editedSection);
706 * @param {!Array.<!Object>} styleRules
707 * @param {!Object.<string, boolean>} usedProperties
708 * @param {?Element} anchorElement
710 _rebuildSectionsForStyleRules: function(styleRules, usedProperties, anchorElement)
712 // Make a property section for each style rule.
714 for (var i = 0; i < styleRules.length; ++i) {
715 var styleRule = styleRules[i];
716 if (styleRule.isStyleSeparator) {
717 var separatorElement = document.createElement("div");
718 if (styleRule.isPlaceholder) {
719 separatorElement.className = "styles-sidebar-placeholder";
720 this._sectionsContainer.insertBefore(separatorElement, anchorElement);
723 separatorElement.className = "sidebar-separator";
724 if (styleRule.node) {
725 var link = WebInspector.DOMPresentationUtils.linkifyNodeReference(styleRule.node);
726 separatorElement.appendChild(document.createTextNode(WebInspector.UIString("Inherited from") + " "));
727 separatorElement.appendChild(link);
728 if (!sections.inheritedPropertiesSeparatorElement)
729 sections.inheritedPropertiesSeparatorElement = separatorElement;
730 } else if ("pseudoId" in styleRule) {
731 var pseudoName = WebInspector.StylesSidebarPane.PseudoIdNames[styleRule.pseudoId];
733 separatorElement.textContent = WebInspector.UIString("Pseudo ::%s element", pseudoName);
735 separatorElement.textContent = WebInspector.UIString("Pseudo element");
737 separatorElement.textContent = styleRule.text;
738 this._sectionsContainer.insertBefore(separatorElement, anchorElement);
741 var computedStyle = styleRule.computedStyle;
743 // Default editable to true if it was omitted.
744 var editable = styleRule.editable;
745 if (typeof editable === "undefined")
749 var section = new WebInspector.ComputedStylePropertiesSection(this, styleRule, usedProperties);
751 var section = new WebInspector.StylePropertiesSection(this, styleRule, editable, styleRule.isInherited);
752 section._markSelectorMatches();
754 section.expanded = true;
757 this._computedStylePane.bodyElement.appendChild(section.element);
759 this._sectionsContainer.insertBefore(section.element, anchorElement);
760 sections.push(section);
765 _containsInherited: function(style)
767 var properties = style.allProperties;
768 for (var i = 0; i < properties.length; ++i) {
769 var property = properties[i];
770 // Does this style contain non-overridden inherited property?
771 if (property.isLive && WebInspector.CSSMetadata.isPropertyInherited(property.name))
777 _colorFormatSettingChanged: function(event)
779 for (var pseudoId in this.sections) {
780 var sections = this.sections[pseudoId];
781 for (var i = 0; i < sections.length; ++i)
782 sections[i].update(true);
787 * @param {?Event} event
789 _createNewRuleInViaInspectorStyleSheet: function(event)
792 var cssModel = this._target.cssModel;
793 cssModel.requestViaInspectorStylesheet(this._node, viaInspectorCallback.bind(this));
796 * @param {?WebInspector.CSSStyleSheetHeader} styleSheetHeader
797 * @this {WebInspector.StylesSidebarPane}
799 function viaInspectorCallback(styleSheetHeader)
801 if (!styleSheetHeader)
803 styleSheetHeader.requestContent(onViaInspectorContent.bind(this, styleSheetHeader.id));
807 * @param {string} styleSheetId
808 * @param {string} text
809 * @this {WebInspector.StylesSidebarPane}
811 function onViaInspectorContent(styleSheetId, text)
813 var lines = text.split("\n");
814 var range = WebInspector.TextRange.createFromLocation(lines.length - 1, lines[lines.length - 1].length);
815 this._addBlankSection(this.sections[0][1], styleSheetId, range);
820 * @param {!WebInspector.StylePropertiesSection} insertAfterSection
821 * @param {string} styleSheetId
822 * @param {!WebInspector.TextRange} ruleLocation
824 _addBlankSection: function(insertAfterSection, styleSheetId, ruleLocation)
827 var blankSection = new WebInspector.BlankStylePropertiesSection(this, this._node ? WebInspector.DOMPresentationUtils.simpleSelector(this._node) : "", styleSheetId, ruleLocation, insertAfterSection.rule);
829 this._sectionsContainer.insertBefore(blankSection.element, insertAfterSection.element.nextSibling);
831 var index = this.sections[0].indexOf(insertAfterSection);
832 this.sections[0].splice(index + 1, 0, blankSection);
833 blankSection.startEditingSelector();
836 removeSection: function(section)
838 for (var pseudoId in this.sections) {
839 var sections = this.sections[pseudoId];
840 var index = sections.indexOf(section);
843 sections.splice(index, 1);
844 section.element.remove();
848 _toggleElementStatePane: function(event)
852 var buttonToggled = !this._elementStateButton.classList.contains("toggled");
855 this._elementStateButton.classList.toggle("toggled", buttonToggled);
856 this._elementStatePane.classList.toggle("expanded", buttonToggled);
859 _createElementStatePane: function()
861 this._elementStatePane = document.createElement("div");
862 this._elementStatePane.className = "styles-element-state-pane source-code";
863 var table = document.createElement("table");
866 this._elementStatePane.inputs = inputs;
869 * @param {!Event} event
870 * @this {WebInspector.StylesSidebarPane}
872 function clickListener(event)
874 var node = this._validateNode();
877 this._setPseudoClassCallback(node, event.target.state, event.target.checked);
881 * @param {string} state
883 * @this {WebInspector.StylesSidebarPane}
885 function createCheckbox(state)
887 var td = document.createElement("td");
888 var label = document.createElement("label");
889 var input = document.createElement("input");
890 input.type = "checkbox";
892 input.addEventListener("click", clickListener.bind(this), false);
894 label.appendChild(input);
895 label.appendChild(document.createTextNode(":" + state));
896 td.appendChild(label);
900 var tr = table.createChild("tr");
901 tr.appendChild(createCheckbox.call(this, "active"));
902 tr.appendChild(createCheckbox.call(this, "hover"));
904 tr = table.createChild("tr");
905 tr.appendChild(createCheckbox.call(this, "focus"));
906 tr.appendChild(createCheckbox.call(this, "visited"));
908 this._elementStatePane.appendChild(table);
914 filterRegex: function()
916 return this._filterRegex;
920 * @param {boolean} isComputedStyleFilter
922 * @param {function(?RegExp)} filterCallback
924 _createPropertyFilterElement: function(isComputedStyleFilter, filterCallback)
926 var input = document.createElement("input");
928 input.placeholder = isComputedStyleFilter ? WebInspector.UIString("Filter") : WebInspector.UIString("Find in Styles");
929 var boundSearchHandler = searchHandler.bind(this);
932 * @this {WebInspector.StylesSidebarPane}
934 function searchHandler()
936 var regex = input.value ? new RegExp(input.value.escapeForRegExp(), "i") : null;
937 filterCallback(regex);
938 input.parentNode.classList.toggle("styles-filter-engaged", !!input.value);
939 this._updateFilter(isComputedStyleFilter);
941 input.addEventListener("input", boundSearchHandler, false);
944 * @param {!Event} event
946 function keydownHandler(event)
949 if (event.keyIdentifier !== Esc || !input.value)
953 boundSearchHandler();
955 input.addEventListener("keydown", keydownHandler, false);
961 * @param {boolean} isComputedStyleFilter
963 _updateFilter: function(isComputedStyleFilter)
965 for (var pseudoId in this.sections) {
966 var sections = this.sections[pseudoId];
967 for (var i = 0; i < sections.length; ++i) {
968 var section = sections[i];
969 if (isComputedStyleFilter !== !!section.computedStyle)
971 section._updateFilter();
977 * @param {!WebInspector.Event} event
979 _showUserAgentStylesSettingChanged: function(event)
981 var showStyles = /** @type {boolean} */ (event.data);
982 this.element.classList.toggle("show-user-styles", showStyles);
987 this._spectrumHelper.hide();
988 this._discardElementUnderMouse();
991 _discardElementUnderMouse: function()
993 if (this._elementUnderMouse)
994 this._elementUnderMouse.classList.remove("styles-panel-hovered");
995 delete this._elementUnderMouse;
998 _mouseMovedOverElement: function(e)
1000 if (this._elementUnderMouse && e.target !== this._elementUnderMouse)
1001 this._discardElementUnderMouse();
1002 this._elementUnderMouse = e.target;
1003 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(e))
1004 this._elementUnderMouse.classList.add("styles-panel-hovered");
1007 _keyDown: function(e)
1009 if ((!WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Ctrl.code) ||
1010 (WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Meta.code)) {
1011 if (this._elementUnderMouse)
1012 this._elementUnderMouse.classList.add("styles-panel-hovered");
1018 if ((!WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Ctrl.code) ||
1019 (WebInspector.isMac() && e.keyCode === WebInspector.KeyboardShortcut.Keys.Meta.code)) {
1020 this._discardElementUnderMouse();
1024 __proto__: WebInspector.SidebarPane.prototype
1029 * @extends {WebInspector.SidebarPane}
1031 WebInspector.ComputedStyleSidebarPane = function()
1033 WebInspector.SidebarPane.call(this, WebInspector.UIString("Computed Style"));
1036 WebInspector.ComputedStyleSidebarPane.prototype = {
1038 * @param {!WebInspector.StylesSidebarPane} pane
1040 setHostingPane: function(pane)
1042 this._stylesSidebarPane = pane;
1045 setFilterBoxContainer: function(element)
1047 element.appendChild(this._stylesSidebarPane._createPropertyFilterElement(true, filterCallback.bind(this)));
1050 * @param {?RegExp} regex
1051 * @this {WebInspector.ComputedStyleSidebarPane}
1053 function filterCallback(regex)
1055 this._filterRegex = regex;
1059 wasShown: function()
1061 WebInspector.SidebarPane.prototype.wasShown.call(this);
1062 if (!this._hasFreshContent)
1063 this.prepareContent();
1067 * @param {function()=} callback
1069 prepareContent: function(callback)
1072 * @this {WebInspector.ComputedStyleSidebarPane}
1074 function wrappedCallback() {
1075 this._hasFreshContent = true;
1078 delete this._hasFreshContent;
1080 this._stylesSidebarPane._refreshUpdate(null, true, wrappedCallback.bind(this));
1086 filterRegex: function()
1088 return this._filterRegex;
1091 __proto__: WebInspector.SidebarPane.prototype
1096 * @extends {WebInspector.PropertiesSection}
1097 * @param {!WebInspector.StylesSidebarPane} parentPane
1098 * @param {!Object} styleRule
1099 * @param {boolean} editable
1100 * @param {boolean} isInherited
1102 WebInspector.StylePropertiesSection = function(parentPane, styleRule, editable, isInherited)
1104 WebInspector.PropertiesSection.call(this, "");
1106 this._parentPane = parentPane;
1107 this.styleRule = styleRule;
1108 this.rule = this.styleRule.rule;
1109 this.editable = editable;
1110 this.isInherited = isInherited;
1112 var extraClasses = (this.rule && (this.rule.isUser || this.rule.isUserAgent) ? " user-rule" : "");
1113 this.element.className = "styles-section matched-styles monospace" + extraClasses;
1114 // We don't really use properties' disclosure.
1115 this.propertiesElement.classList.remove("properties-tree");
1117 var selectorContainer = document.createElement("div");
1118 this._selectorElement = document.createElement("span");
1119 this._selectorElement.textContent = styleRule.selectorText;
1120 selectorContainer.appendChild(this._selectorElement);
1122 var openBrace = document.createElement("span");
1123 openBrace.textContent = " {";
1124 selectorContainer.appendChild(openBrace);
1125 selectorContainer.addEventListener("mousedown", this._handleEmptySpaceMouseDown.bind(this), false);
1126 selectorContainer.addEventListener("click", this._handleSelectorContainerClick.bind(this), false);
1128 var closeBrace = document.createElement("div");
1129 closeBrace.textContent = "}";
1130 this.element.appendChild(closeBrace);
1132 if (this.editable && this.rule) {
1133 var newRuleButton = closeBrace.createChild("div", "sidebar-pane-button-new-rule");
1134 newRuleButton.title = WebInspector.UIString("Insert Style Rule");
1135 newRuleButton.addEventListener("click", this._onNewRuleClick.bind(this), false);
1138 this._selectorElement.addEventListener("click", this._handleSelectorClick.bind(this), false);
1139 this.element.addEventListener("mousedown", this._handleEmptySpaceMouseDown.bind(this), false);
1140 this.element.addEventListener("click", this._handleEmptySpaceClick.bind(this), false);
1143 // Prevent editing the user agent and user rules.
1144 if (this.rule.isUserAgent || this.rule.isUser)
1145 this.editable = false;
1147 // Check this is a real CSSRule, not a bogus object coming from WebInspector.BlankStylePropertiesSection.
1148 if (this.rule.styleSheetId)
1149 this.navigable = !!this.rule.resourceURL();
1151 this.titleElement.classList.add("styles-selector");
1154 this._usedProperties = styleRule.usedProperties;
1156 this._selectorRefElement = document.createElement("div");
1157 this._selectorRefElement.className = "subtitle";
1158 this._mediaListElement = this.titleElement.createChild("div", "media-list");
1159 this._updateMediaList();
1160 this._updateRuleOrigin();
1161 selectorContainer.insertBefore(this._selectorRefElement, selectorContainer.firstChild);
1162 this.titleElement.appendChild(selectorContainer);
1163 this._selectorContainer = selectorContainer;
1166 this.element.classList.add("styles-show-inherited"); // This one is related to inherited rules, not computed style.
1169 this.element.classList.add("navigable");
1172 this.element.classList.add("read-only");
1175 WebInspector.StylePropertiesSection.prototype = {
1177 * @param {?Event} event
1179 _onNewRuleClick: function(event)
1182 var range = WebInspector.TextRange.createFromLocation(this.rule.style.range.endLine, this.rule.style.range.endColumn + 1);
1183 this._parentPane._addBlankSection(this, this.rule.styleSheetId, range);
1187 * @param {!WebInspector.CSSRule} editedRule
1188 * @param {!WebInspector.TextRange} oldRange
1189 * @param {!WebInspector.TextRange} newRange
1191 _styleSheetRuleEdited: function(editedRule, oldRange, newRange)
1193 if (!this.rule || !this.rule.styleSheetId)
1195 if (this.rule !== editedRule)
1196 this.rule.sourceStyleSheetEdited(editedRule.styleSheetId, oldRange, newRange);
1197 this._updateMediaList();
1198 this._updateRuleOrigin();
1202 * @param {!Object} styleRule
1204 _createMediaList: function(styleRule)
1206 if (!styleRule.media)
1208 for (var i = styleRule.media.length - 1; i >= 0; --i) {
1209 var media = styleRule.media[i];
1210 var mediaDataElement = this._mediaListElement.createChild("div", "media");
1212 switch (media.source) {
1213 case WebInspector.CSSMedia.Source.LINKED_SHEET:
1214 case WebInspector.CSSMedia.Source.INLINE_SHEET:
1215 mediaText = "media=\"" + media.text + "\"";
1217 case WebInspector.CSSMedia.Source.MEDIA_RULE:
1218 mediaText = "@media " + media.text;
1220 case WebInspector.CSSMedia.Source.IMPORT_RULE:
1221 mediaText = "@import " + media.text;
1225 if (media.sourceURL) {
1226 var refElement = mediaDataElement.createChild("div", "subtitle");
1227 var anchor = this._parentPane._linkifier.linkifyMedia(media);
1228 anchor.style.float = "right";
1229 refElement.appendChild(anchor);
1232 var mediaTextElement = mediaDataElement.createChild("span");
1233 mediaTextElement.textContent = mediaText;
1234 mediaTextElement.title = media.text;
1238 _updateMediaList: function()
1240 this._mediaListElement.removeChildren();
1241 this._createMediaList(this.styleRule);
1244 collapse: function()
1246 // Overriding with empty body.
1249 handleClick: function()
1251 // Avoid consuming events.
1255 * @param {string} propertyName
1258 isPropertyInherited: function(propertyName)
1260 if (this.isInherited) {
1261 // While rendering inherited stylesheet, reverse meaning of this property.
1262 // Render truly inherited properties with black, i.e. return them as non-inherited.
1263 return !WebInspector.CSSMetadata.isPropertyInherited(propertyName);
1269 * @param {string} propertyName
1270 * @param {boolean=} isShorthand
1273 isPropertyOverloaded: function(propertyName, isShorthand)
1275 if (!this._usedProperties || this.noAffect)
1278 if (this.isInherited && !WebInspector.CSSMetadata.isPropertyInherited(propertyName)) {
1279 // In the inherited sections, only show overrides for the potentially inherited properties.
1283 var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(propertyName);
1284 var used = (canonicalName in this._usedProperties);
1285 if (used || !isShorthand)
1288 // Find out if any of the individual longhand properties of the shorthand
1289 // are used, if none are then the shorthand is overloaded too.
1290 var longhandProperties = this.styleRule.style.longhandProperties(propertyName);
1291 for (var j = 0; j < longhandProperties.length; ++j) {
1292 var individualProperty = longhandProperties[j];
1293 if (WebInspector.CSSMetadata.canonicalPropertyName(individualProperty.name) in this._usedProperties)
1301 * @return {?WebInspector.StylePropertiesSection}
1303 nextEditableSibling: function()
1305 var curSection = this;
1307 curSection = curSection.nextSibling;
1308 } while (curSection && !curSection.editable);
1311 curSection = this.firstSibling;
1312 while (curSection && !curSection.editable)
1313 curSection = curSection.nextSibling;
1316 return (curSection && curSection.editable) ? curSection : null;
1320 * @return {?WebInspector.StylePropertiesSection}
1322 previousEditableSibling: function()
1324 var curSection = this;
1326 curSection = curSection.previousSibling;
1327 } while (curSection && !curSection.editable);
1330 curSection = this.lastSibling;
1331 while (curSection && !curSection.editable)
1332 curSection = curSection.previousSibling;
1335 return (curSection && curSection.editable) ? curSection : null;
1338 update: function(full)
1340 if (this.styleRule.selectorText)
1341 this._selectorElement.textContent = this.styleRule.selectorText;
1342 this._markSelectorMatches();
1344 this.propertiesTreeOutline.removeChildren();
1345 this.populated = false;
1347 var child = this.propertiesTreeOutline.children[0];
1349 child.overloaded = this.isPropertyOverloaded(child.name, child.isShorthand);
1350 child = child.traverseNextTreeElement(false, null, true);
1356 afterUpdate: function()
1358 if (this._afterUpdate) {
1359 this._afterUpdate(this);
1360 delete this._afterUpdate;
1364 onpopulate: function()
1366 var style = this.styleRule.style;
1367 var allProperties = style.allProperties;
1368 this.uniqueProperties = [];
1370 var styleHasEditableSource = this.editable && !!style.range;
1371 if (styleHasEditableSource) {
1372 for (var i = 0; i < allProperties.length; ++i) {
1373 var property = allProperties[i];
1374 this.uniqueProperties.push(property);
1375 if (property.styleBased)
1378 var isShorthand = !!WebInspector.CSSMetadata.cssPropertiesMetainfo.longhands(property.name);
1379 var inherited = this.isPropertyInherited(property.name);
1380 var overloaded = property.inactive || this.isPropertyOverloaded(property.name);
1381 var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, isShorthand, inherited, overloaded);
1382 this.propertiesTreeOutline.appendChild(item);
1387 var generatedShorthands = {};
1388 // For style-based properties, generate shorthands with values when possible.
1389 for (var i = 0; i < allProperties.length; ++i) {
1390 var property = allProperties[i];
1391 this.uniqueProperties.push(property);
1392 var isShorthand = !!WebInspector.CSSMetadata.cssPropertiesMetainfo.longhands(property.name);
1394 // For style-based properties, try generating shorthands.
1395 var shorthands = isShorthand ? null : WebInspector.CSSMetadata.cssPropertiesMetainfo.shorthands(property.name);
1396 var shorthandPropertyAvailable = false;
1397 for (var j = 0; shorthands && !shorthandPropertyAvailable && j < shorthands.length; ++j) {
1398 var shorthand = shorthands[j];
1399 if (shorthand in generatedShorthands) {
1400 shorthandPropertyAvailable = true;
1401 continue; // There already is a shorthand this longhands falls under.
1403 if (style.getLiveProperty(shorthand)) {
1404 shorthandPropertyAvailable = true;
1405 continue; // There is an explict shorthand property this longhands falls under.
1407 if (!style.shorthandValue(shorthand)) {
1408 shorthandPropertyAvailable = false;
1409 continue; // Never generate synthetic shorthands when no value is available.
1412 // Generate synthetic shorthand we have a value for.
1413 var shorthandProperty = new WebInspector.CSSProperty(style, style.allProperties.length, shorthand, style.shorthandValue(shorthand), false, false, true, true);
1414 var overloaded = property.inactive || this.isPropertyOverloaded(property.name, true);
1415 var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, shorthandProperty, /* isShorthand */ true, /* inherited */ false, overloaded);
1416 this.propertiesTreeOutline.appendChild(item);
1417 generatedShorthands[shorthand] = shorthandProperty;
1418 shorthandPropertyAvailable = true;
1420 if (shorthandPropertyAvailable)
1421 continue; // Shorthand for the property found.
1423 var inherited = this.isPropertyInherited(property.name);
1424 var overloaded = property.inactive || this.isPropertyOverloaded(property.name, isShorthand);
1425 var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, isShorthand, inherited, overloaded);
1426 this.propertiesTreeOutline.appendChild(item);
1430 _updateFilter: function()
1432 if (this.styleRule.isAttribute)
1434 var regex = this._parentPane.filterRegex();
1435 var hideRule = regex && !regex.test(this.element.textContent);
1436 this.element.classList.toggle("hidden", hideRule);
1440 var children = this.propertiesTreeOutline.children;
1441 for (var i = 0; i < children.length; ++i)
1442 children[i]._updateFilter();
1444 if (this.styleRule.rule)
1445 this._markSelectorHighlights();
1448 _markSelectorMatches: function()
1450 var rule = this.styleRule.rule;
1454 var matchingSelectors = rule.matchingSelectors;
1455 // .selector is rendered as non-affecting selector by default.
1456 if (this.noAffect || matchingSelectors)
1457 this._selectorElement.className = "selector";
1458 if (!matchingSelectors)
1461 var selectors = rule.selectors;
1462 var fragment = document.createDocumentFragment();
1463 var currentMatch = 0;
1464 for (var i = 0; i < selectors.length ; ++i) {
1466 fragment.appendChild(document.createTextNode(", "));
1467 var isSelectorMatching = matchingSelectors[currentMatch] === i;
1468 if (isSelectorMatching)
1470 var matchingSelectorClass = isSelectorMatching ? " selector-matches" : "";
1471 var selectorElement = document.createElement("span");
1472 selectorElement.className = "simple-selector" + matchingSelectorClass;
1473 if (rule.styleSheetId)
1474 selectorElement._selectorIndex = i;
1475 selectorElement.textContent = selectors[i].value;
1477 fragment.appendChild(selectorElement);
1480 this._selectorElement.removeChildren();
1481 this._selectorElement.appendChild(fragment);
1482 this._markSelectorHighlights();
1485 _markSelectorHighlights: function()
1487 var selectors = this._selectorElement.getElementsByClassName("simple-selector");
1488 var regex = this._parentPane.filterRegex();
1489 for (var i = 0; i < selectors.length; ++i) {
1490 var selectorMatchesFilter = regex && regex.test(selectors[i].textContent);
1491 selectors[i].classList.toggle("filter-match", selectorMatchesFilter);
1495 _checkWillCancelEditing: function()
1497 var willCauseCancelEditing = this._willCauseCancelEditing;
1498 delete this._willCauseCancelEditing;
1499 return willCauseCancelEditing;
1502 _handleSelectorContainerClick: function(event)
1504 if (this._checkWillCancelEditing() || !this.editable)
1506 if (event.target === this._selectorContainer) {
1507 this.addNewBlankProperty(0).startEditing();
1508 event.consume(true);
1513 * @param {number=} index
1514 * @return {!WebInspector.StylePropertyTreeElement}
1516 addNewBlankProperty: function(index)
1518 var style = this.styleRule.style;
1519 var property = style.newBlankProperty(index);
1520 var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, false, false, false);
1521 index = property.index;
1522 this.propertiesTreeOutline.insertChild(item, index);
1523 item.listItemElement.textContent = "";
1524 item._newProperty = true;
1530 * @param {?WebInspector.CSSRule} rule
1531 * @param {!WebInspector.TextRange=} ruleLocation
1534 _createRuleOriginNode: function(rule, ruleLocation)
1537 * @param {string} url
1538 * @param {number} line
1540 function linkifyUncopyable(url, line)
1542 var link = WebInspector.linkifyResourceAsNode(url, line, "", url + ":" + (line + 1));
1543 link.classList.add("webkit-html-resource-link");
1544 link.setAttribute("data-uncopyable", link.textContent);
1545 link.textContent = "";
1550 return document.createTextNode("");
1552 if (!ruleLocation) {
1553 var firstMatchingIndex = rule.matchingSelectors && rule.matchingSelectors.length ? rule.matchingSelectors[0] : 0;
1554 ruleLocation = rule.selectors[firstMatchingIndex].range;
1557 var sourceURL = rule.resourceURL();
1558 if (sourceURL && ruleLocation && rule.styleSheetId) {
1559 var styleSheetHeader = this._parentPane._target.cssModel.styleSheetHeaderForId(rule.styleSheetId);
1560 var lineNumber = styleSheetHeader.lineNumberInSource(ruleLocation.startLine);
1561 var columnNumber = styleSheetHeader.columnNumberInSource(ruleLocation.startLine, ruleLocation.startColumn);
1562 var matchingSelectorLocation = new WebInspector.CSSLocation(this._parentPane._target, rule.styleSheetId, sourceURL, lineNumber, columnNumber);
1563 return this._parentPane._linkifier.linkifyCSSLocation(matchingSelectorLocation) || linkifyUncopyable(sourceURL, 0);
1566 if (rule.isUserAgent)
1567 return document.createTextNode(WebInspector.UIString("user agent stylesheet"));
1569 return document.createTextNode(WebInspector.UIString("user stylesheet"));
1570 if (rule.isViaInspector)
1571 return this._createRuleViaInspectorOriginNode();
1572 return document.createTextNode("");
1578 _createRuleViaInspectorOriginNode: function()
1580 return document.createTextNode(WebInspector.UIString("via inspector"));
1583 _handleEmptySpaceMouseDown: function()
1585 this._willCauseCancelEditing = this._parentPane._isEditingStyle;
1588 _handleEmptySpaceClick: function(event)
1593 if (!window.getSelection().isCollapsed)
1596 if (this._checkWillCancelEditing())
1599 if (event.target.classList.contains("header") || this.element.classList.contains("read-only") || event.target.enclosingNodeOrSelfWithClass("media")) {
1604 this.addNewBlankProperty().startEditing();
1608 _handleSelectorClick: function(event)
1610 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && this.navigable && event.target.classList.contains("simple-selector")) {
1611 var index = event.target._selectorIndex;
1612 var target = this._parentPane._target;
1613 var rawLocation = new WebInspector.CSSLocation(target, this.rule.styleSheetId, this.rule.sourceURL, this.rule.lineNumberInSource(index), this.rule.columnNumberInSource(index));
1614 var uiLocation = WebInspector.cssWorkspaceBinding.rawLocationToUILocation(rawLocation);
1615 WebInspector.Revealer.reveal(uiLocation);
1616 event.consume(true);
1619 this._startEditingOnMouseEvent();
1620 event.consume(true);
1623 _startEditingOnMouseEvent: function()
1628 if (!this.rule && this.propertiesTreeOutline.children.length === 0) {
1630 this.addNewBlankProperty().startEditing();
1637 this.startEditingSelector();
1640 startEditingSelector: function()
1642 var element = this._selectorElement;
1643 if (WebInspector.isBeingEdited(element))
1646 element.scrollIntoViewIfNeeded(false);
1647 element.textContent = element.textContent; // Reset selector marks in group.
1649 var config = new WebInspector.InplaceEditor.Config(this.editingSelectorCommitted.bind(this), this.editingSelectorCancelled.bind(this));
1650 WebInspector.InplaceEditor.startEditing(this._selectorElement, config);
1652 window.getSelection().setBaseAndExtent(element, 0, element, 1);
1653 this._parentPane._isEditingStyle = true;
1656 _moveEditorFromSelector: function(moveDirection)
1658 this._markSelectorMatches();
1663 if (moveDirection === "forward") {
1665 var firstChild = this.propertiesTreeOutline.children[0];
1666 while (firstChild && firstChild.inherited)
1667 firstChild = firstChild.nextSibling;
1669 this.addNewBlankProperty().startEditing();
1671 firstChild.startEditing(firstChild.nameElement);
1673 var previousSection = this.previousEditableSibling();
1674 if (!previousSection)
1677 previousSection.expand();
1678 previousSection.addNewBlankProperty().startEditing();
1682 editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
1684 this._editingSelectorEnded();
1686 newContent = newContent.trim();
1687 if (newContent === oldContent) {
1688 // Revert to a trimmed version of the selector if need be.
1689 this._selectorElement.textContent = newContent;
1690 this._moveEditorFromSelector(moveDirection);
1694 var selectedNode = this._parentPane._node;
1697 * @param {!WebInspector.CSSRule} newRule
1698 * @this {WebInspector.StylePropertiesSection}
1700 function successCallback(newRule)
1702 var doesAffectSelectedNode = newRule.matchingSelectors.length > 0;
1703 if (!doesAffectSelectedNode) {
1704 this.noAffect = true;
1705 this.element.classList.add("no-affect");
1707 delete this.noAffect;
1708 this.element.classList.remove("no-affect");
1711 var oldSelectorRange = this.rule.selectorRange;
1712 this.rule = newRule;
1713 this.styleRule = { section: this, style: newRule.style, selectorText: newRule.selectorText, media: newRule.media, rule: newRule };
1715 this._parentPane.update(selectedNode);
1716 this._parentPane._styleSheetRuleEdited(this.rule, oldSelectorRange, this.rule.selectorRange);
1718 finishOperationAndMoveEditor.call(this, moveDirection);
1722 * @this {WebInspector.StylePropertiesSection}
1724 function finishOperationAndMoveEditor(direction)
1726 delete this._parentPane._userOperation;
1727 this._moveEditorFromSelector(direction);
1730 // This gets deleted in finishOperationAndMoveEditor(), which is called both on success and failure.
1731 this._parentPane._userOperation = true;
1732 this._parentPane._target.cssModel.setRuleSelector(this.rule, selectedNode ? selectedNode.id : 0, newContent, successCallback.bind(this), finishOperationAndMoveEditor.bind(this, moveDirection));
1735 _updateRuleOrigin: function()
1737 this._selectorRefElement.removeChildren();
1738 this._selectorRefElement.appendChild(this._createRuleOriginNode(this.rule));
1741 _editingSelectorEnded: function()
1743 delete this._parentPane._isEditingStyle;
1746 editingSelectorCancelled: function()
1748 this._editingSelectorEnded();
1750 // Mark the selectors in group if necessary.
1751 // This is overridden by BlankStylePropertiesSection.
1752 this._markSelectorMatches();
1755 __proto__: WebInspector.PropertiesSection.prototype
1760 * @extends {WebInspector.PropertiesSection}
1761 * @param {!WebInspector.StylesSidebarPane} stylesPane
1762 * @param {!Object} styleRule
1763 * @param {!Object.<string, boolean>} usedProperties
1765 WebInspector.ComputedStylePropertiesSection = function(stylesPane, styleRule, usedProperties)
1767 WebInspector.PropertiesSection.call(this, "");
1768 this._hasFreshContent = false;
1769 this.element.className = "styles-section monospace read-only computed-style";
1771 var showInheritedCheckbox = WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show inherited properties"), WebInspector.settings.showInheritedComputedStyleProperties, true);
1772 showInheritedCheckbox.classList.add("checkbox-with-label");
1773 this.headerElement.appendChild(showInheritedCheckbox);
1774 WebInspector.settings.showInheritedComputedStyleProperties.addChangeListener(showInheritedChanged.bind(this));
1775 showInheritedChanged.call(this);
1778 * @this {WebInspector.ComputedStylePropertiesSection}
1780 function showInheritedChanged()
1782 this.element.classList.toggle("styles-show-inherited", WebInspector.settings.showInheritedComputedStyleProperties.get());
1785 this._stylesPane = stylesPane;
1786 this.styleRule = styleRule;
1787 this._usedProperties = usedProperties;
1788 this._alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
1789 this.computedStyle = true;
1790 this._propertyTreeElements = {};
1791 this._expandedPropertyNames = {};
1794 WebInspector.ComputedStylePropertiesSection.prototype = {
1795 collapse: function(dontRememberState)
1797 // Overriding with empty body.
1800 _isPropertyInherited: function(propertyName)
1802 var canonicalName = WebInspector.CSSMetadata.canonicalPropertyName(propertyName);
1803 return !(canonicalName in this._usedProperties) && !(canonicalName in this._alwaysShowComputedProperties);
1808 this._expandedPropertyNames = {};
1809 for (var name in this._propertyTreeElements) {
1810 if (this._propertyTreeElements[name].expanded)
1811 this._expandedPropertyNames[name] = true;
1813 this._propertyTreeElements = {};
1814 this.propertiesTreeOutline.removeChildren();
1815 this.populated = false;
1818 _updateFilter: function()
1820 var children = this.propertiesTreeOutline.children;
1821 for (var i = 0; i < children.length; ++i)
1822 children[i]._updateFilter();
1825 onpopulate: function()
1827 function sorter(a, b)
1829 return a.name.compareTo(b.name);
1832 var style = this.styleRule.style;
1836 var uniqueProperties = [];
1837 var allProperties = style.allProperties;
1838 for (var i = 0; i < allProperties.length; ++i)
1839 uniqueProperties.push(allProperties[i]);
1840 uniqueProperties.sort(sorter);
1842 this._propertyTreeElements = {};
1843 for (var i = 0; i < uniqueProperties.length; ++i) {
1844 var property = uniqueProperties[i];
1845 var inherited = this._isPropertyInherited(property.name);
1846 var item = new WebInspector.ComputedStylePropertyTreeElement(this._stylesPane, this.styleRule, style, property, inherited);
1847 this.propertiesTreeOutline.appendChild(item);
1848 this._propertyTreeElements[property.name] = item;
1852 rebuildComputedTrace: function(sections)
1854 for (var i = 0; i < sections.length; ++i) {
1855 var section = sections[i];
1856 if (section.computedStyle || section.isBlank)
1859 for (var j = 0; j < section.uniqueProperties.length; ++j) {
1860 var property = section.uniqueProperties[j];
1861 if (property.disabled)
1863 if (section.isInherited && !WebInspector.CSSMetadata.isPropertyInherited(property.name))
1866 var treeElement = this._propertyTreeElements[property.name.toLowerCase()];
1868 var fragment = document.createDocumentFragment();
1869 var selector = fragment.createChild("span");
1870 selector.style.color = "gray";
1871 selector.textContent = section.styleRule.selectorText;
1872 fragment.appendChild(document.createTextNode(" - " + property.value + " "));
1873 var subtitle = fragment.createChild("span");
1874 subtitle.style.float = "right";
1875 subtitle.appendChild(section._createRuleOriginNode(section.rule));
1876 var childElement = new TreeElement(fragment, null, false);
1877 treeElement.appendChild(childElement);
1878 if (property.inactive || section.isPropertyOverloaded(property.name))
1879 childElement.listItemElement.classList.add("overloaded");
1880 if (!property.parsedOk) {
1881 childElement.listItemElement.classList.add("not-parsed-ok");
1882 childElement.listItemElement.insertBefore(WebInspector.StylesSidebarPane.createExclamationMark(property), childElement.listItemElement.firstChild);
1883 if (WebInspector.StylesSidebarPane._ignoreErrorsForProperty(property))
1884 childElement.listItemElement.classList.add("has-ignorable-error");
1890 // Restore expanded state after update.
1891 for (var name in this._expandedPropertyNames) {
1892 if (name in this._propertyTreeElements)
1893 this._propertyTreeElements[name].expand();
1897 __proto__: WebInspector.PropertiesSection.prototype
1902 * @extends {WebInspector.StylePropertiesSection}
1903 * @param {!WebInspector.StylesSidebarPane} stylesPane
1904 * @param {string} defaultSelectorText
1905 * @param {string} styleSheetId
1906 * @param {!WebInspector.TextRange} ruleLocation
1907 * @param {!WebInspector.CSSRule=} insertAfterRule
1909 WebInspector.BlankStylePropertiesSection = function(stylesPane, defaultSelectorText, styleSheetId, ruleLocation, insertAfterRule)
1911 var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(styleSheetId);
1912 WebInspector.StylePropertiesSection.call(this, stylesPane, { selectorText: defaultSelectorText }, true, false);
1913 this._ruleLocation = ruleLocation;
1914 this._styleSheetId = styleSheetId;
1915 this._selectorRefElement.removeChildren();
1916 if (insertAfterRule) {
1917 this._selectorRefElement.appendChild(this._createRuleOriginNode(insertAfterRule, this._actualRuleLocation()));
1918 this._createMediaList(insertAfterRule);
1920 this._selectorRefElement.appendChild(this._createRuleViaInspectorOriginNode());
1922 this.element.classList.add("blank-section");
1925 WebInspector.BlankStylePropertiesSection.prototype = {
1927 * @return {!WebInspector.TextRange}
1929 _actualRuleLocation: function()
1931 var prefix = this._rulePrefix();
1932 var lines = prefix.split("\n");
1933 var editRange = new WebInspector.TextRange(0, 0, lines.length - 1, lines.peekLast().length);
1934 return this._ruleLocation.rebaseAfterTextEdit(WebInspector.TextRange.createFromLocation(0, 0), editRange);
1940 _rulePrefix: function()
1942 return this._ruleLocation.startLine === 0 && this._ruleLocation.startColumn === 0 ? "" : "\n\n";
1947 return !this._normal;
1953 WebInspector.StylePropertiesSection.prototype.expand.call(this);
1956 editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection)
1958 if (!this.isBlank) {
1959 WebInspector.StylePropertiesSection.prototype.editingSelectorCommitted.call(this, element, newContent, oldContent, context, moveDirection);
1964 * @param {!WebInspector.CSSRule} newRule
1965 * @this {WebInspector.StylePropertiesSection}
1967 function successCallback(newRule)
1969 var doesSelectorAffectSelectedNode = newRule.matchingSelectors.length > 0;
1970 var styleRule = { media: newRule.media, section: this, style: newRule.style, selectorText: newRule.selectorText, rule: newRule };
1971 this._makeNormal(styleRule);
1973 if (!doesSelectorAffectSelectedNode) {
1974 this.noAffect = true;
1975 this.element.classList.add("no-affect");
1978 var ruleTextLines = ruleText.split("\n");
1979 var startLine = this._ruleLocation.startLine;
1980 var startColumn = this._ruleLocation.startColumn;
1981 var newRange = new WebInspector.TextRange(startLine, startColumn, startLine + ruleTextLines.length - 1, startColumn + ruleTextLines[ruleTextLines.length - 1].length);
1982 this._parentPane._styleSheetRuleEdited(newRule, this._ruleLocation, newRange);
1984 this._updateRuleOrigin();
1986 if (this.element.parentElement) // Might have been detached already.
1987 this._moveEditorFromSelector(moveDirection);
1989 delete this._parentPane._userOperation;
1990 this._editingSelectorEnded();
1991 this._markSelectorMatches();
1995 newContent = newContent.trim();
1996 this._parentPane._userOperation = true;
1998 var cssModel = this._parentPane._target.cssModel;
1999 var ruleText = this._rulePrefix() + newContent + " {}";
2000 cssModel.addRule(this._styleSheetId, this._parentPane._node, ruleText, this._ruleLocation, successCallback.bind(this), this.editingSelectorCancelled.bind(this));
2003 editingSelectorCancelled: function()
2005 delete this._parentPane._userOperation;
2006 if (!this.isBlank) {
2007 WebInspector.StylePropertiesSection.prototype.editingSelectorCancelled.call(this);
2011 this._editingSelectorEnded();
2012 this._parentPane.removeSection(this);
2015 _makeNormal: function(styleRule)
2017 this.element.classList.remove("blank-section");
2018 this.styleRule = styleRule;
2019 this.rule = styleRule.rule;
2021 // FIXME: replace this instance by a normal WebInspector.StylePropertiesSection.
2022 this._normal = true;
2025 __proto__: WebInspector.StylePropertiesSection.prototype
2030 * @extends {TreeElement}
2031 * @param {!Object} styleRule
2032 * @param {!WebInspector.CSSStyleDeclaration} style
2033 * @param {!WebInspector.CSSProperty} property
2034 * @param {boolean} inherited
2035 * @param {boolean} overloaded
2036 * @param {boolean} hasChildren
2038 WebInspector.StylePropertyTreeElementBase = function(styleRule, style, property, inherited, overloaded, hasChildren)
2040 this._styleRule = styleRule;
2042 this.property = property;
2043 this._inherited = inherited;
2044 this._overloaded = overloaded;
2046 // Pass an empty title, the title gets made later in onattach.
2047 TreeElement.call(this, "", null, hasChildren);
2049 this.selectable = false;
2052 WebInspector.StylePropertyTreeElementBase.prototype = {
2054 * @return {?WebInspector.DOMNode}
2058 return null; // Overridden by ancestors.
2062 * @return {?WebInspector.StylesSidebarPane}
2064 editablePane: function()
2066 return null; // Overridden by ancestors.
2070 * @return {!WebInspector.StylesSidebarPane|!WebInspector.ComputedStyleSidebarPane}
2072 parentPane: function()
2074 throw "Not implemented";
2079 return this._inherited;
2085 hasIgnorableError: function()
2087 return !this.parsedOk && WebInspector.StylesSidebarPane._ignoreErrorsForProperty(this.property);
2092 if (x === this._inherited)
2094 this._inherited = x;
2100 return this._overloaded;
2105 if (x === this._overloaded)
2107 this._overloaded = x;
2113 return this.property.disabled;
2118 if (!this.disabled || !this.property.text)
2119 return this.property.name;
2121 var text = this.property.text;
2122 var index = text.indexOf(":");
2124 return this.property.name;
2126 text = text.substring(0, index).trim();
2127 if (text.startsWith("/*"))
2128 text = text.substring(2).trim();
2134 if (!this.disabled || !this.property.text)
2135 return this.property.value;
2137 var match = this.property.text.match(/(.*);\s*/);
2138 if (!match || !match[1])
2139 return this.property.value;
2141 var text = match[1];
2142 var index = text.indexOf(":");
2144 return this.property.value;
2146 return text.substring(index + 1).trim();
2151 return this.property.parsedOk;
2154 onattach: function()
2159 updateTitle: function()
2161 var value = this.value;
2165 var nameElement = document.createElement("span");
2166 nameElement.className = "webkit-css-property";
2167 nameElement.textContent = this.name;
2168 nameElement.title = this.property.propertyText;
2169 this.nameElement = nameElement;
2171 this._expandElement = document.createElement("span");
2172 this._expandElement.className = "expand-element";
2174 var valueElement = document.createElement("span");
2175 valueElement.className = "value";
2176 this.valueElement = valueElement;
2179 * @param {!RegExp} regex
2180 * @param {function(string):!Node} processor
2181 * @param {?function(string):!Node} nextProcessor
2182 * @param {string} valueText
2183 * @return {!DocumentFragment}
2185 function processValue(regex, processor, nextProcessor, valueText)
2187 var container = document.createDocumentFragment();
2189 var items = valueText.replace(regex, "\0$1\0").split("\0");
2190 for (var i = 0; i < items.length; ++i) {
2191 if ((i % 2) === 0) {
2193 container.appendChild(nextProcessor(items[i]));
2195 container.appendChild(document.createTextNode(items[i]));
2197 var processedNode = processor(items[i]);
2199 container.appendChild(processedNode);
2207 * @param {string} url
2209 * @this {WebInspector.StylePropertyTreeElementBase}
2211 function linkifyURL(url)
2214 var match = hrefUrl.match(/['"]?([^'"]+)/);
2217 var container = document.createDocumentFragment();
2218 container.appendChild(document.createTextNode("url("));
2219 if (this._styleRule.rule && this._styleRule.rule.resourceURL())
2220 hrefUrl = WebInspector.ParsedURL.completeURL(this._styleRule.rule.resourceURL(), hrefUrl);
2221 else if (this.node())
2222 hrefUrl = this.node().resolveURL(hrefUrl);
2223 var hasResource = hrefUrl && !!WebInspector.resourceForURL(hrefUrl);
2224 // FIXME: WebInspector.linkifyURLAsNode() should really use baseURI.
2225 container.appendChild(WebInspector.linkifyURLAsNode(hrefUrl || url, url, undefined, !hasResource));
2226 container.appendChild(document.createTextNode(")"));
2231 var colorProcessor = processValue.bind(null, WebInspector.StylesSidebarPane._colorRegex, this._processColor.bind(this, nameElement, valueElement), null);
2232 valueElement.appendChild(processValue(/url\(\s*([^)]+)\s*\)/g, linkifyURL.bind(this), WebInspector.CSSMetadata.isColorAwareProperty(this.name) && this.parsedOk ? colorProcessor : null, value));
2235 this.listItemElement.removeChildren();
2236 nameElement.normalize();
2237 valueElement.normalize();
2239 if (!this.treeOutline)
2243 this.listItemElement.createChild("span", "styles-clipboard-only").createTextChild("/* ");
2244 this.listItemElement.appendChild(nameElement);
2245 this.listItemElement.appendChild(document.createTextNode(": "));
2246 this.listItemElement.appendChild(this._expandElement);
2247 this.listItemElement.appendChild(valueElement);
2248 this.listItemElement.appendChild(document.createTextNode(";"));
2250 this.listItemElement.createChild("span", "styles-clipboard-only").createTextChild(" */");
2252 if (!this.parsedOk) {
2253 // Avoid having longhands under an invalid shorthand.
2254 this.hasChildren = false;
2255 this.listItemElement.classList.add("not-parsed-ok");
2257 // Add a separate exclamation mark IMG element with a tooltip.
2258 this.listItemElement.insertBefore(WebInspector.StylesSidebarPane.createExclamationMark(this.property), this.listItemElement.firstChild);
2260 if (this.property.inactive)
2261 this.listItemElement.classList.add("inactive");
2262 this._updateFilter();
2265 _updateFilter: function()
2267 var regEx = this.parentPane().filterRegex();
2268 this.listItemElement.classList.toggle("filter-match", !!regEx && (regEx.test(this.property.name) || regEx.test(this.property.value)));
2272 * @param {!Element} nameElement
2273 * @param {!Element} valueElement
2274 * @param {string} text
2277 _processColor: function(nameElement, valueElement, text)
2279 var color = WebInspector.Color.parse(text);
2281 // We can be called with valid non-color values of |text| (like 'none' from border style)
2283 return document.createTextNode(text);
2285 var format = WebInspector.StylesSidebarPane._colorFormat(color);
2286 var spectrumHelper = this.editablePane() && this.editablePane()._spectrumHelper;
2287 var spectrum = spectrumHelper ? spectrumHelper.spectrum() : null;
2289 var isEditable = !!(this._styleRule && this._styleRule.editable !== false); // |editable| is true by default.
2290 var colorSwatch = new WebInspector.ColorSwatch(!isEditable);
2291 colorSwatch.setColorString(text);
2292 colorSwatch.element.addEventListener("click", swatchClick.bind(this), false);
2294 var scrollerElement;
2295 var boundSpectrumChanged = spectrumChanged.bind(this);
2296 var boundSpectrumHidden = spectrumHidden.bind(this);
2299 * @param {!WebInspector.Event} e
2300 * @this {WebInspector.StylePropertyTreeElementBase}
2302 function spectrumChanged(e)
2304 var colorString = /** @type {string} */ (e.data);
2305 spectrum.displayText = colorString;
2306 colorValueElement.textContent = colorString;
2307 colorSwatch.setColorString(colorString);
2308 this.applyStyleText(nameElement.textContent + ": " + valueElement.textContent, false, false, false);
2312 * @param {!WebInspector.Event} event
2313 * @this {WebInspector.StylePropertyTreeElementBase}
2315 function spectrumHidden(event)
2317 if (scrollerElement)
2318 scrollerElement.removeEventListener("scroll", repositionSpectrum, false);
2319 var commitEdit = event.data;
2320 var propertyText = !commitEdit && this.originalPropertyText ? this.originalPropertyText : (nameElement.textContent + ": " + valueElement.textContent);
2321 this.applyStyleText(propertyText, true, true, false);
2322 spectrum.removeEventListener(WebInspector.Spectrum.Events.ColorChanged, boundSpectrumChanged);
2323 spectrumHelper.removeEventListener(WebInspector.SpectrumPopupHelper.Events.Hidden, boundSpectrumHidden);
2325 delete this.editablePane()._isEditingStyle;
2326 delete this.originalPropertyText;
2329 function repositionSpectrum()
2331 spectrumHelper.reposition(colorSwatch.element);
2336 * @this {WebInspector.StylePropertyTreeElementBase}
2338 function swatchClick(e)
2342 // Shift + click toggles color formats.
2343 // Click opens colorpicker, only if the element is not in computed styles section.
2344 if (!spectrumHelper || e.shiftKey) {
2345 changeColorDisplay();
2352 var visible = spectrumHelper.toggle(colorSwatch.element, color, format);
2354 spectrum.displayText = color.toString(format);
2355 this.originalPropertyText = this.property.propertyText;
2356 this.editablePane()._isEditingStyle = true;
2357 spectrum.addEventListener(WebInspector.Spectrum.Events.ColorChanged, boundSpectrumChanged);
2358 spectrumHelper.addEventListener(WebInspector.SpectrumPopupHelper.Events.Hidden, boundSpectrumHidden);
2360 scrollerElement = colorSwatch.element.enclosingNodeOrSelfWithClass("style-panes-wrapper");
2361 if (scrollerElement)
2362 scrollerElement.addEventListener("scroll", repositionSpectrum, false);
2364 console.error("Unable to handle color picker scrolling");
2368 var colorValueElement = document.createElement("span");
2369 if (format === WebInspector.Color.Format.Original)
2370 colorValueElement.textContent = text;
2372 colorValueElement.textContent = color.toString(format);
2375 * @param {string} curFormat
2377 function nextFormat(curFormat)
2379 // The format loop is as follows:
2383 // * nickname (if the color has a nickname)
2384 // * if the color is simple:
2385 // - shorthex (if has short hex)
2387 var cf = WebInspector.Color.Format;
2389 switch (curFormat) {
2391 return !color.hasAlpha() ? cf.RGB : cf.RGBA;
2395 return !color.hasAlpha() ? cf.HSL : cf.HSLA;
2399 if (color.nickname())
2401 if (!color.hasAlpha())
2402 return color.canBeShortHex() ? cf.ShortHEX : cf.HEX;
2413 if (!color.hasAlpha())
2414 return color.canBeShortHex() ? cf.ShortHEX : cf.HEX;
2423 function changeColorDisplay()
2426 format = nextFormat(format);
2427 var currentValue = color.toString(format);
2428 } while (currentValue === colorValueElement.textContent);
2429 colorValueElement.textContent = currentValue;
2432 var container = document.createElement("nobr");
2433 container.appendChild(colorSwatch.element);
2434 container.appendChild(colorValueElement);
2438 updateState: function()
2440 if (!this.listItemElement)
2443 if (this.style.isPropertyImplicit(this.name))
2444 this.listItemElement.classList.add("implicit");
2446 this.listItemElement.classList.remove("implicit");
2448 if (this.hasIgnorableError())
2449 this.listItemElement.classList.add("has-ignorable-error");
2451 this.listItemElement.classList.remove("has-ignorable-error");
2454 this.listItemElement.classList.add("inherited");
2456 this.listItemElement.classList.remove("inherited");
2458 if (this.overloaded)
2459 this.listItemElement.classList.add("overloaded");
2461 this.listItemElement.classList.remove("overloaded");
2464 this.listItemElement.classList.add("disabled");
2466 this.listItemElement.classList.remove("disabled");
2469 __proto__: TreeElement.prototype
2474 * @extends {WebInspector.StylePropertyTreeElementBase}
2475 * @param {!WebInspector.StylesSidebarPane} stylesPane
2476 * @param {!Object} styleRule
2477 * @param {!WebInspector.CSSStyleDeclaration} style
2478 * @param {!WebInspector.CSSProperty} property
2479 * @param {boolean} inherited
2481 WebInspector.ComputedStylePropertyTreeElement = function(stylesPane, styleRule, style, property, inherited)
2483 WebInspector.StylePropertyTreeElementBase.call(this, styleRule, style, property, inherited, false, false);
2484 this._stylesPane = stylesPane;
2487 WebInspector.ComputedStylePropertyTreeElement.prototype = {
2489 * @return {?WebInspector.DOMNode}
2493 return this._stylesPane._node;
2497 * @return {?WebInspector.StylesSidebarPane}
2499 editablePane: function()
2505 * @return {!WebInspector.ComputedStyleSidebarPane}
2507 parentPane: function()
2509 return this._stylesPane._computedStylePane;
2512 _updateFilter: function()
2514 var regEx = this.parentPane().filterRegex();
2515 this.listItemElement.classList.toggle("hidden", !!regEx && (!regEx.test(this.property.name) && !regEx.test(this.property.value)));
2518 __proto__: WebInspector.StylePropertyTreeElementBase.prototype
2523 * @extends {WebInspector.StylePropertyTreeElementBase}
2524 * @param {!WebInspector.StylesSidebarPane} stylesPane
2525 * @param {!Object} styleRule
2526 * @param {!WebInspector.CSSStyleDeclaration} style
2527 * @param {!WebInspector.CSSProperty} property
2528 * @param {boolean} isShorthand
2529 * @param {boolean} inherited
2530 * @param {boolean} overloaded
2532 WebInspector.StylePropertyTreeElement = function(stylesPane, styleRule, style, property, isShorthand, inherited, overloaded)
2534 WebInspector.StylePropertyTreeElementBase.call(this, styleRule, style, property, inherited, overloaded, isShorthand);
2535 this._parentPane = stylesPane;
2536 this.isShorthand = isShorthand;
2539 WebInspector.StylePropertyTreeElement.prototype = {
2541 * @return {?WebInspector.DOMNode}
2545 return this._parentPane._node;
2549 * @return {?WebInspector.StylesSidebarPane}
2551 editablePane: function()
2553 return this._parentPane;
2557 * @return {!WebInspector.StylesSidebarPane}
2559 parentPane: function()
2561 return this._parentPane;
2565 * @return {?WebInspector.StylePropertiesSection}
2569 return this.treeOutline && this.treeOutline.section;
2573 * @param {function()=} userCallback
2575 _updatePane: function(userCallback)
2577 var section = this.section();
2578 if (section && section._parentPane)
2579 section._parentPane._refreshUpdate(section, false, userCallback);
2587 * @param {!WebInspector.CSSStyleDeclaration} newStyle
2589 _applyNewStyle: function(newStyle)
2591 newStyle.parentRule = this.style.parentRule;
2592 var oldStyleRange = /** @type {!WebInspector.TextRange} */ (this.style.range);
2593 var newStyleRange = /** @type {!WebInspector.TextRange} */ (newStyle.range);
2594 this.style = newStyle;
2595 this._styleRule.style = newStyle;
2596 if (this.style.parentRule) {
2597 this.style.parentRule.style = this.style;
2598 this._parentPane._styleSheetRuleEdited(this.style.parentRule, oldStyleRange, newStyleRange);
2603 * @param {!Event} event
2605 toggleEnabled: function(event)
2607 var disabled = !event.target.checked;
2610 * @param {?WebInspector.CSSStyleDeclaration} newStyle
2611 * @this {WebInspector.StylePropertyTreeElement}
2613 function callback(newStyle)
2615 delete this._parentPane._userOperation;
2619 this._applyNewStyle(newStyle);
2621 var section = this.section();
2622 if (section && section._parentPane)
2623 section._parentPane.dispatchEventToListeners("style property toggled");
2628 this._parentPane._userOperation = true;
2629 this.property.setDisabled(disabled, callback.bind(this));
2633 onpopulate: function()
2635 // Only populate once and if this property is a shorthand.
2636 if (this.children.length || !this.isShorthand)
2639 var longhandProperties = this.style.longhandProperties(this.name);
2640 for (var i = 0; i < longhandProperties.length; ++i) {
2641 var name = longhandProperties[i].name;
2642 var inherited = false;
2643 var overloaded = false;
2645 var section = this.section();
2647 inherited = section.isPropertyInherited(name);
2648 overloaded = section.isPropertyOverloaded(name);
2651 var liveProperty = this.style.getLiveProperty(name);
2655 var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this._styleRule, this.style, liveProperty, false, inherited, overloaded);
2656 this.appendChild(item);
2660 onattach: function()
2662 WebInspector.StylePropertyTreeElementBase.prototype.onattach.call(this);
2664 this.listItemElement.addEventListener("mousedown", this._mouseDown.bind(this));
2665 this.listItemElement.addEventListener("mouseup", this._resetMouseDownElement.bind(this));
2666 this.listItemElement.addEventListener("click", this._mouseClick.bind(this));
2669 _mouseDown: function(event)
2671 if (this._parentPane) {
2672 this._parentPane._mouseDownTreeElement = this;
2673 this._parentPane._mouseDownTreeElementIsName = this._isNameElement(event.target);
2674 this._parentPane._mouseDownTreeElementIsValue = this._isValueElement(event.target);
2678 _resetMouseDownElement: function()
2680 if (this._parentPane) {
2681 delete this._parentPane._mouseDownTreeElement;
2682 delete this._parentPane._mouseDownTreeElementIsName;
2683 delete this._parentPane._mouseDownTreeElementIsValue;
2687 updateTitle: function()
2689 WebInspector.StylePropertyTreeElementBase.prototype.updateTitle.call(this);
2691 if (this.parsedOk && this.section() && this.parent.root) {
2692 var enabledCheckboxElement = document.createElement("input");
2693 enabledCheckboxElement.className = "enabled-button";
2694 enabledCheckboxElement.type = "checkbox";
2695 enabledCheckboxElement.checked = !this.disabled;
2696 enabledCheckboxElement.addEventListener("click", this.toggleEnabled.bind(this), false);
2697 this.listItemElement.insertBefore(enabledCheckboxElement, this.listItemElement.firstChild);
2701 _mouseClick: function(event)
2703 if (!window.getSelection().isCollapsed)
2706 event.consume(true);
2708 if (event.target === this.listItemElement) {
2709 var section = this.section();
2710 if (!section || !section.editable)
2713 if (section._checkWillCancelEditing())
2715 section.addNewBlankProperty(this.property.index + 1).startEditing();
2719 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && this.section().navigable) {
2720 this._navigateToSource(event.target);
2724 this.startEditing(event.target);
2728 * @param {!Element} element
2730 _navigateToSource: function(element)
2732 console.assert(this.section().navigable);
2733 var propertyNameClicked = element === this.nameElement;
2734 WebInspector.Revealer.reveal(WebInspector.cssWorkspaceBinding.propertyUILocation(this.property, propertyNameClicked));
2738 * @param {!Element} element
2740 _isNameElement: function(element)
2742 return element.enclosingNodeOrSelfWithClass("webkit-css-property") === this.nameElement;
2746 * @param {!Element} element
2748 _isValueElement: function(element)
2750 return !!element.enclosingNodeOrSelfWithClass("value");
2754 * @param {?Element=} selectElement
2756 startEditing: function(selectElement)
2758 // FIXME: we don't allow editing of longhand properties under a shorthand right now.
2759 if (this.parent.isShorthand)
2762 if (selectElement === this._expandElement)
2765 var section = this.section();
2766 if (section && !section.editable)
2770 selectElement = this.nameElement; // No arguments passed in - edit the name element by default.
2772 selectElement = selectElement.enclosingNodeOrSelfWithClass("webkit-css-property") || selectElement.enclosingNodeOrSelfWithClass("value");
2774 if (WebInspector.isBeingEdited(selectElement))
2777 var isEditingName = selectElement === this.nameElement;
2779 this.valueElement.textContent = restoreURLs(this.valueElement.textContent, this.value);
2782 * @param {string} fieldValue
2783 * @param {string} modelValue
2786 function restoreURLs(fieldValue, modelValue)
2788 const urlRegex = /\b(url\([^)]*\))/g;
2789 var splitFieldValue = fieldValue.split(urlRegex);
2790 if (splitFieldValue.length === 1)
2792 var modelUrlRegex = new RegExp(urlRegex);
2793 for (var i = 1; i < splitFieldValue.length; i += 2) {
2794 var match = modelUrlRegex.exec(modelValue);
2796 splitFieldValue[i] = match[0];
2798 return splitFieldValue.join("");
2802 expanded: this.expanded,
2803 hasChildren: this.hasChildren,
2804 isEditingName: isEditingName,
2805 previousContent: selectElement.textContent
2808 // Lie about our children to prevent expanding on double click and to collapse shorthands.
2809 this.hasChildren = false;
2811 if (selectElement.parentElement)
2812 selectElement.parentElement.classList.add("child-editing");
2813 selectElement.textContent = selectElement.textContent; // remove color swatch and the like
2816 * @this {WebInspector.StylePropertyTreeElement}
2818 function pasteHandler(context, event)
2820 var data = event.clipboardData.getData("Text");
2823 var colonIdx = data.indexOf(":");
2826 var name = data.substring(0, colonIdx).trim();
2827 var value = data.substring(colonIdx + 1).trim();
2829 event.preventDefault();
2831 if (!("originalName" in context)) {
2832 context.originalName = this.nameElement.textContent;
2833 context.originalValue = this.valueElement.textContent;
2835 this.property.name = name;
2836 this.property.value = value;
2837 this.nameElement.textContent = name;
2838 this.valueElement.textContent = value;
2839 this.nameElement.normalize();
2840 this.valueElement.normalize();
2842 this.editingCommitted(event.target.textContent, context, "forward");
2846 * @this {WebInspector.StylePropertyTreeElement}
2848 function blurListener(context, event)
2850 var treeElement = this._parentPane._mouseDownTreeElement;
2851 var moveDirection = "";
2852 if (treeElement === this) {
2853 if (isEditingName && this._parentPane._mouseDownTreeElementIsValue)
2854 moveDirection = "forward";
2855 if (!isEditingName && this._parentPane._mouseDownTreeElementIsName)
2856 moveDirection = "backward";
2858 this.editingCommitted(event.target.textContent, context, moveDirection);
2861 delete this.originalPropertyText;
2863 this._parentPane._isEditingStyle = true;
2864 if (selectElement.parentElement)
2865 selectElement.parentElement.scrollIntoViewIfNeeded(false);
2867 var applyItemCallback = !isEditingName ? this._applyFreeFlowStyleTextEdit.bind(this, true) : undefined;
2868 this._prompt = new WebInspector.StylesSidebarPane.CSSPropertyPrompt(isEditingName ? WebInspector.CSSMetadata.cssPropertiesMetainfo : WebInspector.CSSMetadata.keywordsForProperty(this.nameElement.textContent), this, isEditingName);
2869 if (applyItemCallback) {
2870 this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemApplied, applyItemCallback, this);
2871 this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemAccepted, applyItemCallback, this);
2873 var proxyElement = this._prompt.attachAndStartEditing(selectElement, blurListener.bind(this, context));
2875 proxyElement.addEventListener("keydown", this.editingNameValueKeyDown.bind(this, context), false);
2876 proxyElement.addEventListener("keypress", this.editingNameValueKeyPress.bind(this, context), false);
2878 proxyElement.addEventListener("paste", pasteHandler.bind(this, context), false);
2880 window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1);
2883 editingNameValueKeyDown: function(context, event)
2888 var isEditingName = context.isEditingName;
2891 if (isEnterKey(event)) {
2892 event.preventDefault();
2894 } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
2896 else if (!isEditingName && this._newProperty && event.keyCode === WebInspector.KeyboardShortcut.Keys.Backspace.code) {
2897 // For a new property, when Backspace is pressed at the beginning of new property value, move back to the property name.
2898 var selection = window.getSelection();
2899 if (selection.isCollapsed && !selection.focusOffset) {
2900 event.preventDefault();
2901 result = "backward";
2903 } else if (event.keyIdentifier === "U+0009") { // Tab key.
2904 result = event.shiftKey ? "backward" : "forward";
2905 event.preventDefault();
2911 this.editingCancelled(null, context);
2915 this.editingCommitted(event.target.textContent, context, result);
2924 this._applyFreeFlowStyleTextEdit(false);
2927 editingNameValueKeyPress: function(context, event)
2929 function shouldCommitValueSemicolon(text, cursorPosition)
2931 // FIXME: should this account for semicolons inside comments?
2933 for (var i = 0; i < cursorPosition; ++i) {
2935 if (ch === "\\" && openQuote !== "")
2936 ++i; // skip next character inside string
2937 else if (!openQuote && (ch === "\"" || ch === "'"))
2939 else if (openQuote === ch)
2945 var keyChar = String.fromCharCode(event.charCode);
2946 var isFieldInputTerminated = (context.isEditingName ? keyChar === ":" : keyChar === ";" && shouldCommitValueSemicolon(event.target.textContent, event.target.selectionLeftOffset()));
2947 if (isFieldInputTerminated) {
2948 // Enter or colon (for name)/semicolon outside of string (for value).
2949 event.consume(true);
2950 this.editingCommitted(event.target.textContent, context, "forward");
2955 _applyFreeFlowStyleTextEdit: function(now)
2957 if (this._applyFreeFlowStyleTextEditTimer)
2958 clearTimeout(this._applyFreeFlowStyleTextEditTimer);
2961 * @this {WebInspector.StylePropertyTreeElement}
2965 var valueText = this.valueElement.textContent;
2966 if (valueText.indexOf(";") === -1)
2967 this.applyStyleText(this.nameElement.textContent + ": " + valueText, false, false, false);
2972 this._applyFreeFlowStyleTextEditTimer = setTimeout(apply.bind(this), 100);
2975 kickFreeFlowStyleEditForTest: function()
2977 this._applyFreeFlowStyleTextEdit(true);
2980 editingEnded: function(context)
2982 this._resetMouseDownElement();
2983 if (this._applyFreeFlowStyleTextEditTimer)
2984 clearTimeout(this._applyFreeFlowStyleTextEditTimer);
2986 this.hasChildren = context.hasChildren;
2987 if (context.expanded)
2989 var editedElement = context.isEditingName ? this.nameElement : this.valueElement;
2990 // The proxyElement has been deleted, no need to remove listener.
2991 if (editedElement.parentElement)
2992 editedElement.parentElement.classList.remove("child-editing");
2994 delete this._parentPane._isEditingStyle;
2997 editingCancelled: function(element, context)
2999 this._removePrompt();
3000 this._revertStyleUponEditingCanceled(this.originalPropertyText);
3001 // This should happen last, as it clears the info necessary to restore the property value after [Page]Up/Down changes.
3002 this.editingEnded(context);
3005 _revertStyleUponEditingCanceled: function(originalPropertyText)
3007 if (typeof originalPropertyText === "string") {
3008 delete this.originalPropertyText;
3009 this.applyStyleText(originalPropertyText, true, false, true);
3011 if (this._newProperty)
3012 this.treeOutline.removeChild(this);
3018 _findSibling: function(moveDirection)
3022 target = (moveDirection === "forward" ? target.nextSibling : target.previousSibling);
3023 } while(target && target.inherited);
3029 * @param {string} userInput
3030 * @param {!Object} context
3031 * @param {string} moveDirection
3033 editingCommitted: function(userInput, context, moveDirection)
3035 this._removePrompt();
3036 this.editingEnded(context);
3037 var isEditingName = context.isEditingName;
3039 // Determine where to move to before making changes
3040 var createNewProperty, moveToPropertyName, moveToSelector;
3041 var isDataPasted = "originalName" in context;
3042 var isDirtyViaPaste = isDataPasted && (this.nameElement.textContent !== context.originalName || this.valueElement.textContent !== context.originalValue);
3043 var isPropertySplitPaste = isDataPasted && isEditingName && this.valueElement.textContent !== context.originalValue;
3045 var moveToOther = (isEditingName ^ (moveDirection === "forward"));
3046 var abandonNewProperty = this._newProperty && !userInput && (moveToOther || isEditingName);
3047 if (moveDirection === "forward" && (!isEditingName || isPropertySplitPaste) || moveDirection === "backward" && isEditingName) {
3048 moveTo = moveTo._findSibling(moveDirection);
3050 moveToPropertyName = moveTo.name;
3051 else if (moveDirection === "forward" && (!this._newProperty || userInput))
3052 createNewProperty = true;
3053 else if (moveDirection === "backward")
3054 moveToSelector = true;
3057 // Make the Changes and trigger the moveToNextCallback after updating.
3058 var moveToIndex = moveTo && this.treeOutline ? this.treeOutline.children.indexOf(moveTo) : -1;
3059 var blankInput = /^\s*$/.test(userInput);
3060 var shouldCommitNewProperty = this._newProperty && (isPropertySplitPaste || moveToOther || (!moveDirection && !isEditingName) || (isEditingName && blankInput));
3061 var section = this.section();
3062 if (((userInput !== context.previousContent || isDirtyViaPaste) && !this._newProperty) || shouldCommitNewProperty) {
3063 section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput, section);
3065 if (blankInput || (this._newProperty && /^\s*$/.test(this.valueElement.textContent)))
3069 propertyText = userInput + ": " + this.property.value;
3071 propertyText = this.property.name + ": " + userInput;
3073 this.applyStyleText(propertyText, true, true, false);
3076 this.property.name = userInput;
3078 this.property.value = userInput;
3079 if (!isDataPasted && !this._newProperty)
3081 moveToNextCallback.call(this, this._newProperty, false, section);
3085 * The Callback to start editing the next/previous property/selector.
3086 * @this {WebInspector.StylePropertyTreeElement}
3088 function moveToNextCallback(alreadyNew, valueChanged, section)
3093 // User just tabbed through without changes.
3094 if (moveTo && moveTo.parent) {
3095 moveTo.startEditing(!isEditingName ? moveTo.nameElement : moveTo.valueElement);
3099 // User has made a change then tabbed, wiping all the original treeElements.
3100 // Recalculate the new treeElement for the same property we were going to edit next.
3101 if (moveTo && !moveTo.parent) {
3102 var propertyElements = section.propertiesTreeOutline.children;
3103 if (moveDirection === "forward" && blankInput && !isEditingName)
3105 if (moveToIndex >= propertyElements.length && !this._newProperty)
3106 createNewProperty = true;
3108 var treeElement = moveToIndex >= 0 ? propertyElements[moveToIndex] : null;
3110 var elementToEdit = !isEditingName || isPropertySplitPaste ? treeElement.nameElement : treeElement.valueElement;
3111 if (alreadyNew && blankInput)
3112 elementToEdit = moveDirection === "forward" ? treeElement.nameElement : treeElement.valueElement;
3113 treeElement.startEditing(elementToEdit);
3115 } else if (!alreadyNew)
3116 moveToSelector = true;
3120 // Create a new attribute in this section (or move to next editable selector if possible).
3121 if (createNewProperty) {
3122 if (alreadyNew && !valueChanged && (isEditingName ^ (moveDirection === "backward")))
3125 section.addNewBlankProperty().startEditing();
3129 if (abandonNewProperty) {
3130 moveTo = this._findSibling(moveDirection);
3131 var sectionToEdit = (moveTo || moveDirection === "backward") ? section : section.nextEditableSibling();
3132 if (sectionToEdit) {
3133 if (sectionToEdit.rule)
3134 sectionToEdit.startEditingSelector();
3136 sectionToEdit._moveEditorFromSelector(moveDirection);
3141 if (moveToSelector) {
3143 section.startEditingSelector();
3145 section._moveEditorFromSelector(moveDirection);
3150 _removePrompt: function()
3152 // BUG 53242. This cannot go into editingEnded(), as it should always happen first for any editing outcome.
3154 this._prompt.detach();
3155 delete this._prompt;
3159 _hasBeenModifiedIncrementally: function()
3161 // New properties applied via up/down or live editing have an originalPropertyText and will be deleted later
3162 // on, if cancelled, when the empty string gets applied as their style text.
3163 return typeof this.originalPropertyText === "string" || (!!this.property.propertyText && this._newProperty);
3166 styleTextAppliedForTest: function()
3170 applyStyleText: function(styleText, updateInterface, majorChange, isRevert)
3172 function userOperationFinishedCallback(parentPane, updateInterface)
3174 if (updateInterface)
3175 delete parentPane._userOperation;
3178 // Leave a way to cancel editing after incremental changes.
3179 if (!isRevert && !updateInterface && !this._hasBeenModifiedIncrementally()) {
3180 // Remember the rule's original CSS text on [Page](Up|Down), so it can be restored
3181 // if the editing is canceled.
3182 this.originalPropertyText = this.property.propertyText;
3185 if (!this.treeOutline)
3188 var section = this.section();
3189 styleText = styleText.replace(/\s/g, " ").trim(); // Replace with whitespace.
3190 var styleTextLength = styleText.length;
3191 if (!styleTextLength && updateInterface && !isRevert && this._newProperty && !this._hasBeenModifiedIncrementally()) {
3192 // The user deleted everything and never applied a new property value via Up/Down scrolling/live editing, so remove the tree element and update.
3193 this.parent.removeChild(this);
3194 section.afterUpdate();
3198 var currentNode = this._parentPane._node;
3199 if (updateInterface)
3200 this._parentPane._userOperation = true;
3203 * @param {function()} userCallback
3204 * @param {string} originalPropertyText
3205 * @param {?WebInspector.CSSStyleDeclaration} newStyle
3206 * @this {WebInspector.StylePropertyTreeElement}
3208 function callback(userCallback, originalPropertyText, newStyle)
3211 if (updateInterface) {
3212 // It did not apply, cancel editing.
3213 this._revertStyleUponEditingCanceled(originalPropertyText);
3218 this._applyNewStyle(newStyle);
3220 if (this._newProperty)
3221 this._newPropertyInStyle = true;
3223 this.property = newStyle.propertyAt(this.property.index);
3224 if (section && section._parentPane)
3225 section._parentPane.dispatchEventToListeners("style edited");
3227 if (updateInterface && currentNode === this.node()) {
3228 this._updatePane(userCallback);
3229 this.styleTextAppliedForTest();
3234 this.styleTextAppliedForTest();
3237 // Append a ";" if the new text does not end in ";".
3238 // FIXME: this does not handle trailing comments.
3239 if (styleText.length && !/;\s*$/.test(styleText))
3241 var overwriteProperty = !!(!this._newProperty || this._newPropertyInStyle);
3242 this.property.setText(styleText, majorChange, overwriteProperty, callback.bind(this, userOperationFinishedCallback.bind(null, this._parentPane, updateInterface), this.originalPropertyText));
3248 ondblclick: function()
3250 return true; // handled
3254 * @param {!Event} event
3257 isEventWithinDisclosureTriangle: function(event)
3259 return event.target === this._expandElement;
3262 __proto__: WebInspector.StylePropertyTreeElementBase.prototype
3267 * @extends {WebInspector.TextPrompt}
3268 * @param {!WebInspector.CSSMetadata} cssCompletions
3269 * @param {!WebInspector.StylePropertyTreeElement} sidebarPane
3270 * @param {boolean} isEditingName
3272 WebInspector.StylesSidebarPane.CSSPropertyPrompt = function(cssCompletions, sidebarPane, isEditingName)
3274 // Use the same callback both for applyItemCallback and acceptItemCallback.
3275 WebInspector.TextPrompt.call(this, this._buildPropertyCompletions.bind(this), WebInspector.StyleValueDelimiters);
3276 this.setSuggestBoxEnabled(true);
3277 this._cssCompletions = cssCompletions;
3278 this._sidebarPane = sidebarPane;
3279 this._isEditingName = isEditingName;
3282 this.disableDefaultSuggestionForEmptyInput();
3285 WebInspector.StylesSidebarPane.CSSPropertyPrompt.prototype = {
3287 * @param {!Event} event
3289 onKeyDown: function(event)
3291 switch (event.keyIdentifier) {
3296 if (this._handleNameOrValueUpDown(event)) {
3297 event.preventDefault();
3302 if (this.autoCompleteElement && !this.autoCompleteElement.textContent.length) {
3303 this.tabKeyPressed();
3309 WebInspector.TextPrompt.prototype.onKeyDown.call(this, event);
3312 onMouseWheel: function(event)
3314 if (this._handleNameOrValueUpDown(event)) {
3315 event.consume(true);
3318 WebInspector.TextPrompt.prototype.onMouseWheel.call(this, event);
3325 tabKeyPressed: function()
3327 this.acceptAutoComplete();
3329 // Always tab to the next field.
3334 * @param {!Event} event
3337 _handleNameOrValueUpDown: function(event)
3340 * @param {string} originalValue
3341 * @param {string} replacementString
3342 * @this {WebInspector.StylesSidebarPane.CSSPropertyPrompt}
3344 function finishHandler(originalValue, replacementString)
3346 // Synthesize property text disregarding any comments, custom whitespace etc.
3347 this._sidebarPane.applyStyleText(this._sidebarPane.nameElement.textContent + ": " + this._sidebarPane.valueElement.textContent, false, false, false);
3351 * @param {string} prefix
3352 * @param {number} number
3353 * @param {string} suffix
3355 * @this {WebInspector.StylesSidebarPane.CSSPropertyPrompt}
3357 function customNumberHandler(prefix, number, suffix)
3359 if (number !== 0 && !suffix.length && WebInspector.CSSMetadata.isLengthProperty(this._sidebarPane.property.name))
3361 return prefix + number + suffix;
3364 // Handle numeric value increment/decrement only at this point.
3365 if (!this._isEditingName && WebInspector.handleElementValueModifications(event, this._sidebarPane.valueElement, finishHandler.bind(this), this._isValueSuggestion.bind(this), customNumberHandler.bind(this)))
3372 * @param {string} word
3375 _isValueSuggestion: function(word)
3379 word = word.toLowerCase();
3380 return this._cssCompletions.keySet().hasOwnProperty(word);
3384 * @param {!Element} proxyElement
3385 * @param {!Range} wordRange
3386 * @param {boolean} force
3387 * @param {function(!Array.<string>, number=)} completionsReadyCallback
3389 _buildPropertyCompletions: function(proxyElement, wordRange, force, completionsReadyCallback)
3391 var prefix = wordRange.toString().toLowerCase();
3392 if (!prefix && !force && (this._isEditingName || proxyElement.textContent.length)) {
3393 completionsReadyCallback([]);
3397 var results = this._cssCompletions.startsWith(prefix);
3398 var userEnteredText = wordRange.toString().replace("-", "");
3399 if (userEnteredText && (userEnteredText === userEnteredText.toUpperCase())) {
3400 for (var i = 0; i < results.length; ++i)
3401 results[i] = results[i].toUpperCase();
3403 var selectedIndex = this._cssCompletions.mostUsedOf(results);
3404 completionsReadyCallback(results, selectedIndex);
3407 __proto__: WebInspector.TextPrompt.prototype