editing-session.js (6131B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /** 8 * An instance of EditingSession tracks changes that have been made during the 9 * modification of box model values. All of these changes can be reverted by 10 * calling revert. 11 */ 12 class EditingSession { 13 /** 14 * @param {object} options 15 * @param {InspectorPanel} options.inspector 16 * The inspector panel. 17 * @param {Document} options.doc 18 * A DOM document that can be used to test style rules. 19 * @param {Array} options.elementRules 20 * An array of the style rules defined for the node being 21 * edited. These should be in order of priority, least 22 * important first. 23 */ 24 constructor({ inspector, doc, elementRules }) { 25 this._doc = doc; 26 this._inspector = inspector; 27 this._rules = elementRules; 28 this._modifications = new Map(); 29 } 30 /** 31 * Gets the value of a single property from the CSS rule. 32 * 33 * @param {StyleRuleFront} rule 34 * The CSS rule. 35 * @param {string} property 36 * The name of the property. 37 * @return {string} the value. 38 */ 39 getPropertyFromRule(rule, property) { 40 // Use the parsed declarations in the StyleRuleFront object if available. 41 const index = this.getPropertyIndex(property, rule); 42 if (index !== -1) { 43 return rule.declarations[index].value; 44 } 45 46 // Fallback to parsing the cssText locally otherwise. 47 const dummyStyle = this._element.style; 48 dummyStyle.cssText = rule.cssText; 49 return dummyStyle.getPropertyValue(property); 50 } 51 52 /** 53 * Returns the current value for a property as a string or the empty string if 54 * no style rules affect the property. 55 * 56 * @param {string} property 57 * The name of the property as a string 58 */ 59 getProperty(property) { 60 // Create a hidden element for getPropertyFromRule to use 61 const div = this._doc.createElement("div"); 62 div.setAttribute("style", "display: none"); 63 this._doc.getElementById("inspector-main-content").appendChild(div); 64 this._element = this._doc.createElement("p"); 65 div.appendChild(this._element); 66 67 // As the rules are in order of priority we can just iterate until we find 68 // the first that defines a value for the property and return that. 69 for (const rule of this._rules) { 70 const value = this.getPropertyFromRule(rule, property); 71 if (value !== "") { 72 div.remove(); 73 return value; 74 } 75 } 76 div.remove(); 77 return ""; 78 } 79 80 /** 81 * Get the index of a given css property name in a CSS rule. 82 * Or -1, if there are no properties in the rule yet. 83 * 84 * @param {string} name 85 * The property name. 86 * @param {StyleRuleFront} rule 87 * Optional, defaults to the element style rule. 88 * @return {number} The property index in the rule. 89 */ 90 getPropertyIndex(name, rule = this._rules[0]) { 91 if (!rule.declarations.length) { 92 return -1; 93 } 94 95 return rule.declarations.findIndex(p => p.name === name); 96 } 97 98 /** 99 * Sets a number of properties on the node. 100 * 101 * @param {Array} properties 102 * An array of properties, each is an object with name and 103 * value properties. If the value is "" then the property 104 * is removed. 105 * @return {Promise} Resolves when the modifications are complete. 106 */ 107 async setProperties(properties) { 108 for (const property of properties) { 109 // Get a RuleModificationList or RuleRewriter helper object from the 110 // StyleRuleActor to make changes to CSS properties. 111 // Note that RuleRewriter doesn't support modifying several properties at 112 // once, so we do this in a sequence here. 113 const modifications = this._rules[0].startModifyingProperties( 114 this._inspector.panelWin, 115 this._inspector.cssProperties 116 ); 117 118 // Remember the property so it can be reverted. 119 if (!this._modifications.has(property.name)) { 120 this._modifications.set( 121 property.name, 122 this.getPropertyFromRule(this._rules[0], property.name) 123 ); 124 } 125 126 // Find the index of the property to be changed, or get the next index to 127 // insert the new property at. 128 let index = this.getPropertyIndex(property.name); 129 if (index === -1) { 130 index = this._rules[0].declarations.length; 131 } 132 133 if (property.value == "") { 134 modifications.removeProperty(index, property.name); 135 } else { 136 modifications.setProperty(index, property.name, property.value, ""); 137 } 138 139 await modifications.apply(); 140 } 141 } 142 143 /** 144 * Reverts all of the property changes made by this instance. 145 * 146 * @return {Promise} Resolves when all properties have been reverted. 147 */ 148 async revert() { 149 // Revert each property that we modified previously, one by one. See 150 // setProperties for information about why. 151 for (const [property, value] of this._modifications) { 152 const modifications = this._rules[0].startModifyingProperties( 153 this._inspector.panelWin, 154 this._inspector.cssProperties 155 ); 156 157 // Find the index of the property to be reverted. 158 let index = this.getPropertyIndex(property); 159 160 if (value != "") { 161 // If the property doesn't exist anymore, insert at the beginning of the 162 // rule. 163 if (index === -1) { 164 index = 0; 165 } 166 modifications.setProperty(index, property, value, ""); 167 } else { 168 // If the property doesn't exist anymore, no need to remove it. It had 169 // not been added after all. 170 if (index === -1) { 171 continue; 172 } 173 modifications.removeProperty(index, property); 174 } 175 176 await modifications.apply(); 177 } 178 } 179 180 destroy() { 181 this._modifications.clear(); 182 183 this._cssProperties = null; 184 this._doc = null; 185 this._inspector = null; 186 this._modifications = null; 187 this._rules = null; 188 } 189 } 190 191 module.exports = EditingSession;