/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const {escapeCSSComment} = require("devtools/shared/css/parsing-utils"); const {getCssProperties} = require("devtools/shared/fronts/css-properties"); /** * TextProperty is responsible for the following: * Manages a single property from the authoredText attribute of the * relevant declaration. * Maintains a list of computed properties that come from this * property declaration. * Changes to the TextProperty are sent to its related Rule for * application. * * @param {Rule} rule * The rule this TextProperty came from. * @param {String} name * The text property name (such as "background" or "border-top"). * @param {String} value * The property's value (not including priority). * @param {String} priority * The property's priority (either "important" or an empty string). * @param {Boolean} enabled * Whether the property is enabled. * @param {Boolean} invisible * Whether the property is invisible. An invisible property * does not show up in the UI; these are needed so that the * index of a property in Rule.textProps is the same as the index * coming from parseDeclarations. */ function TextProperty(rule, name, value, priority, enabled = true, invisible = false) { this.rule = rule; this.name = name; this.value = value; this.priority = priority; this.enabled = !!enabled; this.invisible = invisible; this.panelDoc = this.rule.elementStyle.ruleView.inspector.panelDoc; const toolbox = this.rule.elementStyle.ruleView.inspector.toolbox; this.cssProperties = getCssProperties(toolbox); this.updateComputed(); } TextProperty.prototype = { /** * Update the editor associated with this text property, * if any. */ updateEditor: function () { if (this.editor) { this.editor.update(); } }, /** * Update the list of computed properties for this text property. */ updateComputed: function () { if (!this.name) { return; } // This is a bit funky. To get the list of computed properties // for this text property, we'll set the property on a dummy element // and see what the computed style looks like. let dummyElement = this.rule.elementStyle.ruleView.dummyElement; let dummyStyle = dummyElement.style; dummyStyle.cssText = ""; dummyStyle.setProperty(this.name, this.value, this.priority); this.computed = []; // Manually get all the properties that are set when setting a value on // this.name and check the computed style on dummyElement for each one. // If we just read dummyStyle, it would skip properties when value === "". let subProps = this.cssProperties.getSubproperties(this.name); for (let prop of subProps) { this.computed.push({ textProp: this, name: prop, value: dummyStyle.getPropertyValue(prop), priority: dummyStyle.getPropertyPriority(prop), }); } }, /** * Set all the values from another TextProperty instance into * this TextProperty instance. * * @param {TextProperty} prop * The other TextProperty instance. */ set: function (prop) { let changed = false; for (let item of ["name", "value", "priority", "enabled"]) { if (this[item] !== prop[item]) { this[item] = prop[item]; changed = true; } } if (changed) { this.updateEditor(); } }, setValue: function (value, priority, force = false) { let store = this.rule.elementStyle.store; if (this.editor && value !== this.editor.committed.value || force) { store.userProperties.setProperty(this.rule.style, this.name, value); } this.rule.setPropertyValue(this, value, priority); this.updateEditor(); }, /** * Called when the property's value has been updated externally, and * the property and editor should update. */ noticeNewValue: function (value) { if (value !== this.value) { this.value = value; this.updateEditor(); } }, setName: function (name) { let store = this.rule.elementStyle.store; if (name !== this.name) { store.userProperties.setProperty(this.rule.style, name, this.editor.committed.value); } this.rule.setPropertyName(this, name); this.updateEditor(); }, setEnabled: function (value) { this.rule.setPropertyEnabled(this, value); this.updateEditor(); }, remove: function () { this.rule.removeProperty(this); }, /** * Return a string representation of the rule property. */ stringifyProperty: function () { // Get the displayed property value let declaration = this.name + ": " + this.editor.valueSpan.textContent + ";"; // Comment out property declarations that are not enabled if (!this.enabled) { declaration = "/* " + escapeCSSComment(declaration) + " */"; } return declaration; }, /** * See whether this property's name is known. * * @return {Boolean} true if the property name is known, false otherwise. */ isKnownProperty: function () { return this.cssProperties.isKnown(this.name); }, /** * Validate this property. Does it make sense for this value to be assigned * to this property name? * * @return {Boolean} true if the property value is valid, false otherwise. */ isValid: function () { // Starting with FF49, StyleRuleActors provide a list of parsed // declarations, with data about their validity, but if we don't have this, // compute validity locally (which might not be correct, but better than // nothing). if (!this.rule.domRule.declarations) { return this.cssProperties.isValidOnClient(this.name, this.value, this.panelDoc); } let selfIndex = this.rule.textProps.indexOf(this); // When adding a new property in the rule-view, the TextProperty object is // created right away before the rule gets updated on the server, so we're // not going to find the corresponding declaration object yet. Default to // true. if (!this.rule.domRule.declarations[selfIndex]) { return true; } return this.rule.domRule.declarations[selfIndex].isValid; } }; exports.TextProperty = TextProperty;