diff options
Diffstat (limited to 'WebCore/inspector/front-end/StylesSidebarPane.js')
-rw-r--r-- | WebCore/inspector/front-end/StylesSidebarPane.js | 927 |
1 files changed, 927 insertions, 0 deletions
diff --git a/WebCore/inspector/front-end/StylesSidebarPane.js b/WebCore/inspector/front-end/StylesSidebarPane.js new file mode 100644 index 0000000..f0a9afb --- /dev/null +++ b/WebCore/inspector/front-end/StylesSidebarPane.js @@ -0,0 +1,927 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.StylesSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles")); +} + +WebInspector.StylesSidebarPane.prototype = { + update: function(node, editedSection, forceUpdate) + { + var refresh = false; + + if (forceUpdate) + delete this.node; + + if (!forceUpdate && (!node || node === this.node)) + refresh = true; + + if (node && node.nodeType === Node.TEXT_NODE && node.parentNode) + node = node.parentNode; + + if (node && node.nodeType !== Node.ELEMENT_NODE) + node = null; + + if (node) + this.node = node; + else + node = this.node; + + var body = this.bodyElement; + if (!refresh || !node) { + body.removeChildren(); + this.sections = []; + } + + if (!node) + return; + + var styleRules = []; + + if (refresh) { + for (var i = 0; i < this.sections.length; ++i) { + var section = this.sections[i]; + if (section.computedStyle) + section.styleRule.style = node.ownerDocument.defaultView.getComputedStyle(node); + var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle }; + styleRules.push(styleRule); + } + } else { + var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node); + styleRules.push({ computedStyle: true, selectorText: WebInspector.UIString("Computed Style"), style: computedStyle, editable: false }); + + var nodeName = node.nodeName.toLowerCase(); + for (var i = 0; i < node.attributes.length; ++i) { + var attr = node.attributes[i]; + if (attr.style) { + var attrStyle = { style: attr.style, editable: false }; + attrStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", attr.name); + attrStyle.selectorText = nodeName + "[" + attr.name; + if (attr.value.length) + attrStyle.selectorText += "=" + attr.value; + attrStyle.selectorText += "]"; + styleRules.push(attrStyle); + } + } + + if (node.style && (node.style.length || Object.hasProperties(node.style.__disabledProperties))) { + var inlineStyle = { selectorText: WebInspector.UIString("Inline Style Attribute"), style: node.style }; + inlineStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", "style"); + styleRules.push(inlineStyle); + } + + var matchedStyleRules = node.ownerDocument.defaultView.getMatchedCSSRules(node, "", !Preferences.showUserAgentStyles); + if (matchedStyleRules) { + // Add rules in reverse order to match the cascade order. + for (var i = (matchedStyleRules.length - 1); i >= 0; --i) { + var rule = matchedStyleRules[i]; + styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet }); + } + } + } + + function deleteDisabledProperty(style, name) + { + if (!style || !name) + return; + if (style.__disabledPropertyValues) + delete style.__disabledPropertyValues[name]; + if (style.__disabledPropertyPriorities) + delete style.__disabledPropertyPriorities[name]; + if (style.__disabledProperties) + delete style.__disabledProperties[name]; + } + + var usedProperties = {}; + var disabledComputedProperties = {}; + var priorityUsed = false; + + // Walk the style rules and make a list of all used and overloaded properties. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + if (styleRule.computedStyle) + continue; + + styleRule.usedProperties = {}; + + var style = styleRule.style; + for (var j = 0; j < style.length; ++j) { + var name = style[j]; + + if (!priorityUsed && style.getPropertyPriority(name).length) + priorityUsed = true; + + // If the property name is already used by another rule then this rule's + // property is overloaded, so don't add it to the rule's usedProperties. + if (!(name in usedProperties)) + styleRule.usedProperties[name] = true; + + if (name === "font") { + // The font property is not reported as a shorthand. Report finding the individual + // properties so they are visible in computed style. + // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15598 is fixed. + styleRule.usedProperties["font-family"] = true; + styleRule.usedProperties["font-size"] = true; + styleRule.usedProperties["font-style"] = true; + styleRule.usedProperties["font-variant"] = true; + styleRule.usedProperties["font-weight"] = true; + styleRule.usedProperties["line-height"] = true; + } + + // Delete any disabled properties, since the property does exist. + // This prevents it from showing twice. + deleteDisabledProperty(style, name); + deleteDisabledProperty(style, style.getPropertyShorthand(name)); + } + + // Add all the properties found in this style to the used properties list. + // Do this here so only future rules are affect by properties used in this rule. + for (var name in styleRules[i].usedProperties) + usedProperties[name] = true; + + // Remember all disabled properties so they show up in computed style. + if (style.__disabledProperties) + for (var name in style.__disabledProperties) + disabledComputedProperties[name] = true; + } + + if (priorityUsed) { + // Walk the properties again and account for !important. + var foundPriorityProperties = []; + + // Walk in reverse to match the order !important overrides. + for (var i = (styleRules.length - 1); i >= 0; --i) { + if (styleRules[i].computedStyle) + continue; + + var style = styleRules[i].style; + var uniqueProperties = getUniqueStyleProperties(style); + for (var j = 0; j < uniqueProperties.length; ++j) { + var name = uniqueProperties[j]; + if (style.getPropertyPriority(name).length) { + if (!(name in foundPriorityProperties)) + styleRules[i].usedProperties[name] = true; + else + delete styleRules[i].usedProperties[name]; + foundPriorityProperties[name] = true; + } else if (name in foundPriorityProperties) + delete styleRules[i].usedProperties[name]; + } + } + } + + if (refresh) { + // Walk the style rules and update the sections with new overloaded and used properties. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + var section = styleRule.section; + if (styleRule.computedStyle) + section.disabledComputedProperties = disabledComputedProperties; + section._usedProperties = (styleRule.usedProperties || usedProperties); + section.update((section === editedSection) || styleRule.computedStyle); + } + } else { + // Make a property section for each style rule. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + var subtitle = styleRule.subtitle; + delete styleRule.subtitle; + + var computedStyle = styleRule.computedStyle; + delete styleRule.computedStyle; + + var ruleUsedProperties = styleRule.usedProperties; + delete styleRule.usedProperties; + + var editable = styleRule.editable; + delete styleRule.editable; + + // Default editable to true if it was omitted. + if (typeof editable === "undefined") + editable = true; + + var section = new WebInspector.StylePropertiesSection(styleRule, subtitle, computedStyle, (ruleUsedProperties || usedProperties), editable); + if (computedStyle) + section.disabledComputedProperties = disabledComputedProperties; + section.pane = this; + + if (Preferences.styleRulesExpandedState && section.identifier in Preferences.styleRulesExpandedState) + section.expanded = Preferences.styleRulesExpandedState[section.identifier]; + else if (computedStyle) + section.collapse(true); + else + section.expand(true); + + body.appendChild(section.element); + this.sections.push(section); + } + } + } +} + +WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyle, usedProperties, editable) +{ + WebInspector.PropertiesSection.call(this, styleRule.selectorText); + + this.styleRule = styleRule; + this.computedStyle = computedStyle; + this.editable = (editable && !computedStyle); + + // Prevent editing the user agent and user rules. + var isUserAgent = this.styleRule.parentStyleSheet && !this.styleRule.parentStyleSheet.ownerNode && !this.styleRule.parentStyleSheet.href; + var isUser = this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.ownerNode && this.styleRule.parentStyleSheet.ownerNode.nodeName == '#document'; + if (isUserAgent || isUser) + this.editable = false; + + this._usedProperties = usedProperties; + + if (computedStyle) { + this.element.addStyleClass("computed-style"); + + if (Preferences.showInheritedComputedStyleProperties) + this.element.addStyleClass("show-inherited"); + + var showInheritedLabel = document.createElement("label"); + var showInheritedInput = document.createElement("input"); + showInheritedInput.type = "checkbox"; + showInheritedInput.checked = Preferences.showInheritedComputedStyleProperties; + + var computedStyleSection = this; + var showInheritedToggleFunction = function(event) { + Preferences.showInheritedComputedStyleProperties = showInheritedInput.checked; + if (Preferences.showInheritedComputedStyleProperties) + computedStyleSection.element.addStyleClass("show-inherited"); + else + computedStyleSection.element.removeStyleClass("show-inherited"); + event.stopPropagation(); + }; + + showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false); + + showInheritedLabel.appendChild(showInheritedInput); + showInheritedLabel.appendChild(document.createTextNode(WebInspector.UIString("Show inherited"))); + this.subtitleElement.appendChild(showInheritedLabel); + } else { + if (!subtitle) { + if (this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.href) { + var url = this.styleRule.parentStyleSheet.href; + subtitle = WebInspector.linkifyURL(url, WebInspector.displayNameForURL(url)); + this.subtitleElement.addStyleClass("file"); + } else if (isUserAgent) + subtitle = WebInspector.UIString("user agent stylesheet"); + else if (isUser) + subtitle = WebInspector.UIString("user stylesheet"); + else + subtitle = WebInspector.UIString("inline stylesheet"); + } + + this.subtitle = subtitle; + } + + this.identifier = styleRule.selectorText; + if (this.subtitle) + this.identifier += ":" + this.subtitleElement.textContent; +} + +WebInspector.StylePropertiesSection.prototype = { + get usedProperties() + { + return this._usedProperties || {}; + }, + + set usedProperties(x) + { + this._usedProperties = x; + this.update(); + }, + + expand: function(dontRememberState) + { + WebInspector.PropertiesSection.prototype.expand.call(this); + if (dontRememberState) + return; + + if (!Preferences.styleRulesExpandedState) + Preferences.styleRulesExpandedState = {}; + Preferences.styleRulesExpandedState[this.identifier] = true; + }, + + collapse: function(dontRememberState) + { + WebInspector.PropertiesSection.prototype.collapse.call(this); + if (dontRememberState) + return; + + if (!Preferences.styleRulesExpandedState) + Preferences.styleRulesExpandedState = {}; + Preferences.styleRulesExpandedState[this.identifier] = false; + }, + + isPropertyInherited: function(property) + { + if (!this.computedStyle || !this._usedProperties) + return false; + // These properties should always show for Computed Style. + var alwaysShowComputedProperties = { "display": true, "height": true, "width": true }; + return !(property in this.usedProperties) && !(property in alwaysShowComputedProperties) && !(property in this.disabledComputedProperties); + }, + + isPropertyOverloaded: function(property, shorthand) + { + if (this.computedStyle || !this._usedProperties) + return false; + + var used = (property in this.usedProperties); + if (used || !shorthand) + return !used; + + // Find out if any of the individual longhand properties of the shorthand + // are used, if none are then the shorthand is overloaded too. + var longhandProperties = getLonghandProperties(this.styleRule.style, property); + for (var j = 0; j < longhandProperties.length; ++j) { + var individualProperty = longhandProperties[j]; + if (individualProperty in this.usedProperties) + return false; + } + + return true; + }, + + update: function(full) + { + if (full || this.computedStyle) { + this.propertiesTreeOutline.removeChildren(); + this.populated = false; + } else { + var child = this.propertiesTreeOutline.children[0]; + while (child) { + child.overloaded = this.isPropertyOverloaded(child.name, child.shorthand); + child = child.traverseNextTreeElement(false, null, true); + } + } + }, + + onpopulate: function() + { + var style = this.styleRule.style; + + var foundShorthands = {}; + var uniqueProperties = getUniqueStyleProperties(style); + var disabledProperties = style.__disabledPropertyValues || {}; + + for (var name in disabledProperties) + uniqueProperties.push(name); + + uniqueProperties.sort(); + + for (var i = 0; i < uniqueProperties.length; ++i) { + var name = uniqueProperties[i]; + var disabled = name in disabledProperties; + if (!disabled && this.disabledComputedProperties && !(name in this.usedProperties) && name in this.disabledComputedProperties) + disabled = true; + + var shorthand = !disabled ? style.getPropertyShorthand(name) : null; + + if (shorthand && shorthand in foundShorthands) + continue; + + if (shorthand) { + foundShorthands[shorthand] = true; + name = shorthand; + } + + var isShorthand = (shorthand ? true : false); + var inherited = this.isPropertyInherited(name); + var overloaded = this.isPropertyOverloaded(name, isShorthand); + + var item = new WebInspector.StylePropertyTreeElement(style, name, isShorthand, inherited, overloaded, disabled); + this.propertiesTreeOutline.appendChild(item); + } + } +} + +WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; + +WebInspector.StylePropertyTreeElement = function(style, name, shorthand, inherited, overloaded, disabled) +{ + this.style = style; + this.name = name; + this.shorthand = shorthand; + this._inherited = inherited; + this._overloaded = overloaded; + this._disabled = disabled; + + // Pass an empty title, the title gets made later in onattach. + TreeElement.call(this, "", null, shorthand); +} + +WebInspector.StylePropertyTreeElement.prototype = { + get inherited() + { + return this._inherited; + }, + + set inherited(x) + { + if (x === this._inherited) + return; + this._inherited = x; + this.updateState(); + }, + + get overloaded() + { + return this._overloaded; + }, + + set overloaded(x) + { + if (x === this._overloaded) + return; + this._overloaded = x; + this.updateState(); + }, + + get disabled() + { + return this._disabled; + }, + + set disabled(x) + { + if (x === this._disabled) + return; + this._disabled = x; + this.updateState(); + }, + + get priority() + { + if (this.disabled && this.style.__disabledPropertyPriorities && this.name in this.style.__disabledPropertyPriorities) + return this.style.__disabledPropertyPriorities[this.name]; + return (this.shorthand ? getShorthandPriority(this.style, this.name) : this.style.getPropertyPriority(this.name)); + }, + + get value() + { + if (this.disabled && this.style.__disabledPropertyValues && this.name in this.style.__disabledPropertyValues) + return this.style.__disabledPropertyValues[this.name]; + return (this.shorthand ? getShorthandValue(this.style, this.name) : this.style.getPropertyValue(this.name)); + }, + + onattach: function() + { + this.updateTitle(); + }, + + updateTitle: function() + { + // "Nicknames" for some common values that are easier to read. + var valueNicknames = { + "rgb(0, 0, 0)": "black", + "#000": "black", + "#000000": "black", + "rgb(255, 255, 255)": "white", + "#fff": "white", + "#ffffff": "white", + "#FFF": "white", + "#FFFFFF": "white", + "rgba(0, 0, 0, 0)": "transparent", + "rgb(255, 0, 0)": "red", + "rgb(0, 255, 0)": "lime", + "rgb(0, 0, 255)": "blue", + "rgb(255, 255, 0)": "yellow", + "rgb(255, 0, 255)": "magenta", + "rgb(0, 255, 255)": "cyan" + }; + + var priority = this.priority; + var value = this.value; + var htmlValue = value; + + if (priority && !priority.length) + delete priority; + if (priority) + priority = "!" + priority; + + if (value) { + var urls = value.match(/url\([^)]+\)/); + if (urls) { + for (var i = 0; i < urls.length; ++i) { + var url = urls[i].substring(4, urls[i].length - 1); + htmlValue = htmlValue.replace(urls[i], "url(" + WebInspector.linkifyURL(url) + ")"); + } + } else { + if (value in valueNicknames) + htmlValue = valueNicknames[value]; + htmlValue = htmlValue.escapeHTML(); + } + } else + htmlValue = value = ""; + + this.updateState(); + + var enabledCheckboxElement = document.createElement("input"); + enabledCheckboxElement.className = "enabled-button"; + enabledCheckboxElement.type = "checkbox"; + enabledCheckboxElement.checked = !this.disabled; + enabledCheckboxElement.addEventListener("change", this.toggleEnabled.bind(this), false); + + var nameElement = document.createElement("span"); + nameElement.className = "name"; + nameElement.textContent = this.name; + + var valueElement = document.createElement("span"); + valueElement.className = "value"; + valueElement.innerHTML = htmlValue; + + if (priority) { + var priorityElement = document.createElement("span"); + priorityElement.className = "priority"; + priorityElement.textContent = priority; + } + + this.listItemElement.removeChildren(); + + // Append the checkbox for root elements of an editable section. + if (this.treeOutline.section && this.treeOutline.section.editable && this.parent.root) + this.listItemElement.appendChild(enabledCheckboxElement); + this.listItemElement.appendChild(nameElement); + this.listItemElement.appendChild(document.createTextNode(": ")); + this.listItemElement.appendChild(valueElement); + + if (priorityElement) { + this.listItemElement.appendChild(document.createTextNode(" ")); + this.listItemElement.appendChild(priorityElement); + } + + this.listItemElement.appendChild(document.createTextNode(";")); + + if (value) { + // FIXME: this dosen't catch keyword based colors like black and white + var colors = value.match(/((rgb|hsl)a?\([^)]+\))|(#[0-9a-fA-F]{6})|(#[0-9a-fA-F]{3})/g); + if (colors) { + var colorsLength = colors.length; + for (var i = 0; i < colorsLength; ++i) { + var swatchElement = document.createElement("span"); + swatchElement.className = "swatch"; + swatchElement.style.setProperty("background-color", colors[i]); + this.listItemElement.appendChild(swatchElement); + } + } + } + + this.tooltip = this.name + ": " + (valueNicknames[value] || value) + (priority ? " " + priority : ""); + }, + + updateAll: function(updateAllRules) + { + if (updateAllRules && this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.update(null, this.treeOutline.section); + else if (this.treeOutline.section) + this.treeOutline.section.update(true); + else + this.updateTitle(); // FIXME: this will not show new properties. But we don't hit his case yet. + }, + + toggleEnabled: function(event) + { + var disabled = !event.target.checked; + + if (disabled) { + if (!this.style.__disabledPropertyValues || !this.style.__disabledPropertyPriorities) { + var inspectedWindow = InspectorController.inspectedWindow(); + this.style.__disabledProperties = new inspectedWindow.Object; + this.style.__disabledPropertyValues = new inspectedWindow.Object; + this.style.__disabledPropertyPriorities = new inspectedWindow.Object; + } + + this.style.__disabledPropertyValues[this.name] = this.value; + this.style.__disabledPropertyPriorities[this.name] = this.priority; + + if (this.shorthand) { + var longhandProperties = getLonghandProperties(this.style, this.name); + for (var i = 0; i < longhandProperties.length; ++i) { + this.style.__disabledProperties[longhandProperties[i]] = true; + this.style.removeProperty(longhandProperties[i]); + } + } else { + this.style.__disabledProperties[this.name] = true; + this.style.removeProperty(this.name); + } + } else { + this.style.setProperty(this.name, this.value, this.priority); + delete this.style.__disabledProperties[this.name]; + delete this.style.__disabledPropertyValues[this.name]; + delete this.style.__disabledPropertyPriorities[this.name]; + } + + // Set the disabled property here, since the code above replies on it not changing + // until after the value and priority are retrieved. + this.disabled = disabled; + + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.dispatchEventToListeners("style property toggled"); + + this.updateAll(true); + }, + + updateState: function() + { + if (!this.listItemElement) + return; + + if (this.style.isPropertyImplicit(this.name) || this.value === "initial") + this.listItemElement.addStyleClass("implicit"); + else + this.listItemElement.removeStyleClass("implicit"); + + if (this.inherited) + this.listItemElement.addStyleClass("inherited"); + else + this.listItemElement.removeStyleClass("inherited"); + + if (this.overloaded) + this.listItemElement.addStyleClass("overloaded"); + else + this.listItemElement.removeStyleClass("overloaded"); + + if (this.disabled) + this.listItemElement.addStyleClass("disabled"); + else + this.listItemElement.removeStyleClass("disabled"); + }, + + onpopulate: function() + { + // Only populate once and if this property is a shorthand. + if (this.children.length || !this.shorthand) + return; + + var longhandProperties = getLonghandProperties(this.style, this.name); + for (var i = 0; i < longhandProperties.length; ++i) { + var name = longhandProperties[i]; + + if (this.treeOutline.section) { + var inherited = this.treeOutline.section.isPropertyInherited(name); + var overloaded = this.treeOutline.section.isPropertyOverloaded(name); + } + + var item = new WebInspector.StylePropertyTreeElement(this.style, name, false, inherited, overloaded); + this.appendChild(item); + } + }, + + ondblclick: function(element, event) + { + this.startEditing(event.target); + }, + + startEditing: function(selectElement) + { + // FIXME: we don't allow editing of longhand properties under a shorthand right now. + if (this.parent.shorthand) + return; + + if (WebInspector.isBeingEdited(this.listItemElement) || (this.treeOutline.section && !this.treeOutline.section.editable)) + return; + + var context = { expanded: this.expanded, hasChildren: this.hasChildren }; + + // Lie about our children to prevent expanding on double click and to collapse shorthands. + this.hasChildren = false; + + if (!selectElement) + selectElement = this.listItemElement; + + this.listItemElement.handleKeyEvent = this.editingKeyDown.bind(this); + + WebInspector.startEditing(this.listItemElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context); + window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1); + }, + + editingKeyDown: function(event) + { + var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down"); + var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown"); + if (!arrowKeyPressed && !pageKeyPressed) + return; + + var selection = window.getSelection(); + if (!selection.rangeCount) + return; + + var selectionRange = selection.getRangeAt(0); + if (selectionRange.commonAncestorContainer !== this.listItemElement && !selectionRange.commonAncestorContainer.isDescendant(this.listItemElement)) + return; + + const styleValueDelimeters = " \t\n\"':;,/()"; + var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, styleValueDelimeters, this.listItemElement); + var wordString = wordRange.toString(); + var replacementString = wordString; + + var matches = /(.*?)(-?\d+(?:\.\d+)?)(.*)/.exec(wordString); + if (matches && matches.length) { + var prefix = matches[1]; + var number = parseFloat(matches[2]); + var suffix = matches[3]; + + // If the number is near zero or the number is one and the direction will take it near zero. + var numberNearZero = (number < 1 && number > -1); + if (number === 1 && event.keyIdentifier === "Down") + numberNearZero = true; + else if (number === -1 && event.keyIdentifier === "Up") + numberNearZero = true; + + if (numberNearZero && event.altKey && arrowKeyPressed) { + if (event.keyIdentifier === "Down") + number = Math.ceil(number - 1); + else + number = Math.floor(number + 1); + } else { + // Jump by 10 when shift is down or jump by 0.1 when near zero or Alt/Option is down. + // Also jump by 10 for page up and down, or by 100 if shift is held with a page key. + var changeAmount = 1; + if (event.shiftKey && pageKeyPressed) + changeAmount = 100; + else if (event.shiftKey || pageKeyPressed) + changeAmount = 10; + else if (event.altKey || numberNearZero) + changeAmount = 0.1; + + if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown") + changeAmount *= -1; + + // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns. + // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1. + number = Number((number + changeAmount).toFixed(6)); + } + + replacementString = prefix + number + suffix; + } else { + // FIXME: this should cycle through known keywords for the current property name. + return; + } + + var replacementTextNode = document.createTextNode(replacementString); + + wordRange.deleteContents(); + wordRange.insertNode(replacementTextNode); + + var finalSelectionRange = document.createRange(); + finalSelectionRange.setStart(replacementTextNode, 0); + finalSelectionRange.setEnd(replacementTextNode, replacementString.length); + + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + + event.preventDefault(); + event.handled = true; + + if (!this.originalCSSText) { + // Remember the rule's original CSS text, so it can be restored + // if the editing is canceled and before each apply. + this.originalCSSText = getStyleTextWithShorthands(this.style); + } else { + // Restore the original CSS text before applying user changes. This is needed to prevent + // new properties from sticking around if the user adds one, then removes it. + this.style.cssText = this.originalCSSText; + } + + this.applyStyleText(this.listItemElement.textContent); + }, + + editingEnded: function(context) + { + this.hasChildren = context.hasChildren; + if (context.expanded) + this.expand(); + delete this.listItemElement.handleKeyEvent; + delete this.originalCSSText; + }, + + editingCancelled: function(element, context) + { + if (this.originalCSSText) { + this.style.cssText = this.originalCSSText; + + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.dispatchEventToListeners("style edited"); + + this.updateAll(); + } else + this.updateTitle(); + + this.editingEnded(context); + }, + + editingCommitted: function(element, userInput, previousContent, context) + { + this.editingEnded(context); + + if (userInput === previousContent) + return; // nothing changed, so do nothing else + + this.applyStyleText(userInput, true); + }, + + applyStyleText: function(styleText, updateInterface) + { + var styleTextLength = styleText.trimWhitespace().length; + + // Create a new element to parse the user input CSS. + var parseElement = document.createElement("span"); + parseElement.setAttribute("style", styleText); + + var tempStyle = parseElement.style; + if (tempStyle.length || !styleTextLength) { + // The input was parsable or the user deleted everything, so remove the + // original property from the real style declaration. If this represents + // a shorthand remove all the longhand properties. + if (this.shorthand) { + var longhandProperties = getLonghandProperties(this.style, this.name); + for (var i = 0; i < longhandProperties.length; ++i) + this.style.removeProperty(longhandProperties[i]); + } else + this.style.removeProperty(this.name); + } + + if (!styleTextLength) { + if (updateInterface) { + // The user deleted the everything, so remove the tree element and update. + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.update(); + this.parent.removeChild(this); + } + return; + } + + if (!tempStyle.length) { + // The user typed something, but it didn't parse. Just abort and restore + // the original title for this property. + if (updateInterface) + this.updateTitle(); + return; + } + + // Iterate of the properties on the test element's style declaration and + // add them to the real style declaration. We take care to move shorthands. + var foundShorthands = {}; + var uniqueProperties = getUniqueStyleProperties(tempStyle); + for (var i = 0; i < uniqueProperties.length; ++i) { + var name = uniqueProperties[i]; + var shorthand = tempStyle.getPropertyShorthand(name); + + if (shorthand && shorthand in foundShorthands) + continue; + + if (shorthand) { + var value = getShorthandValue(tempStyle, shorthand); + var priority = getShorthandPriority(tempStyle, shorthand); + foundShorthands[shorthand] = true; + } else { + var value = tempStyle.getPropertyValue(name); + var priority = tempStyle.getPropertyPriority(name); + } + + // Set the property on the real style declaration. + this.style.setProperty((shorthand || name), value, priority); + } + + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.dispatchEventToListeners("style edited"); + + if (updateInterface) + this.updateAll(true); + } +} + +WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype; |