diff options
Diffstat (limited to 'devtools/client/inspector/boxmodel/utils/editing-session.js')
-rw-r--r-- | devtools/client/inspector/boxmodel/utils/editing-session.js | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/devtools/client/inspector/boxmodel/utils/editing-session.js b/devtools/client/inspector/boxmodel/utils/editing-session.js new file mode 100644 index 0000000000..3175375f22 --- /dev/null +++ b/devtools/client/inspector/boxmodel/utils/editing-session.js @@ -0,0 +1,188 @@ +/* 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"; + +/** + * An instance of EditingSession tracks changes that have been made during the + * modification of box model values. All of these changes can be reverted by + * calling revert. + * + * @param {InspectorPanel} inspector + * The inspector panel. + * @param {Document} doc + * A DOM document that can be used to test style rules. + * @param {Array} rules + * An array of the style rules defined for the node being + * edited. These should be in order of priority, least + * important first. + */ +function EditingSession({ inspector, doc, elementRules }) { + this._doc = doc; + this._inspector = inspector; + this._rules = elementRules; + this._modifications = new Map(); +} + +EditingSession.prototype = { + /** + * Gets the value of a single property from the CSS rule. + * + * @param {StyleRuleFront} rule + * The CSS rule. + * @param {String} property + * The name of the property. + * @return {String} the value. + */ + getPropertyFromRule(rule, property) { + // Use the parsed declarations in the StyleRuleFront object if available. + const index = this.getPropertyIndex(property, rule); + if (index !== -1) { + return rule.declarations[index].value; + } + + // Fallback to parsing the cssText locally otherwise. + const dummyStyle = this._element.style; + dummyStyle.cssText = rule.cssText; + return dummyStyle.getPropertyValue(property); + }, + + /** + * Returns the current value for a property as a string or the empty string if + * no style rules affect the property. + * + * @param {String} property + * The name of the property as a string + */ + getProperty(property) { + // Create a hidden element for getPropertyFromRule to use + const div = this._doc.createElement("div"); + div.setAttribute("style", "display: none"); + this._doc.getElementById("inspector-main-content").appendChild(div); + this._element = this._doc.createElement("p"); + div.appendChild(this._element); + + // As the rules are in order of priority we can just iterate until we find + // the first that defines a value for the property and return that. + for (const rule of this._rules) { + const value = this.getPropertyFromRule(rule, property); + if (value !== "") { + div.remove(); + return value; + } + } + div.remove(); + return ""; + }, + + /** + * Get the index of a given css property name in a CSS rule. + * Or -1, if there are no properties in the rule yet. + * + * @param {String} name + * The property name. + * @param {StyleRuleFront} rule + * Optional, defaults to the element style rule. + * @return {Number} The property index in the rule. + */ + getPropertyIndex(name, rule = this._rules[0]) { + if (!rule.declarations.length) { + return -1; + } + + return rule.declarations.findIndex(p => p.name === name); + }, + + /** + * Sets a number of properties on the node. + * + * @param {Array} properties + * An array of properties, each is an object with name and + * value properties. If the value is "" then the property + * is removed. + * @return {Promise} Resolves when the modifications are complete. + */ + async setProperties(properties) { + for (const property of properties) { + // Get a RuleModificationList or RuleRewriter helper object from the + // StyleRuleActor to make changes to CSS properties. + // Note that RuleRewriter doesn't support modifying several properties at + // once, so we do this in a sequence here. + const modifications = this._rules[0].startModifyingProperties( + this._inspector.cssProperties + ); + + // Remember the property so it can be reverted. + if (!this._modifications.has(property.name)) { + this._modifications.set( + property.name, + this.getPropertyFromRule(this._rules[0], property.name) + ); + } + + // Find the index of the property to be changed, or get the next index to + // insert the new property at. + let index = this.getPropertyIndex(property.name); + if (index === -1) { + index = this._rules[0].declarations.length; + } + + if (property.value == "") { + modifications.removeProperty(index, property.name); + } else { + modifications.setProperty(index, property.name, property.value, ""); + } + + await modifications.apply(); + } + }, + + /** + * Reverts all of the property changes made by this instance. + * + * @return {Promise} Resolves when all properties have been reverted. + */ + async revert() { + // Revert each property that we modified previously, one by one. See + // setProperties for information about why. + for (const [property, value] of this._modifications) { + const modifications = this._rules[0].startModifyingProperties( + this._inspector.cssProperties + ); + + // Find the index of the property to be reverted. + let index = this.getPropertyIndex(property); + + if (value != "") { + // If the property doesn't exist anymore, insert at the beginning of the + // rule. + if (index === -1) { + index = 0; + } + modifications.setProperty(index, property, value, ""); + } else { + // If the property doesn't exist anymore, no need to remove it. It had + // not been added after all. + if (index === -1) { + continue; + } + modifications.removeProperty(index, property); + } + + await modifications.apply(); + } + }, + + destroy() { + this._modifications.clear(); + + this._cssProperties = null; + this._doc = null; + this._inspector = null; + this._modifications = null; + this._rules = null; + }, +}; + +module.exports = EditingSession; |