style-rule.js (10117B)
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 const { 8 FrontClassWithSpec, 9 registerFront, 10 } = require("resource://devtools/shared/protocol.js"); 11 const { 12 styleRuleSpec, 13 } = require("resource://devtools/shared/specs/style-rule.js"); 14 15 loader.lazyRequireGetter( 16 this, 17 "RuleRewriter", 18 "resource://devtools/client/fronts/inspector/rule-rewriter.js" 19 ); 20 21 /** 22 * StyleRuleFront, the front for the StyleRule actor. 23 */ 24 class StyleRuleFront extends FrontClassWithSpec(styleRuleSpec) { 25 constructor(client, targetFront, parentFront) { 26 super(client, targetFront, parentFront); 27 28 this.before("location-changed", this._locationChangedPre.bind(this)); 29 } 30 31 form(form) { 32 this.actorID = form.actor; 33 this._form = form; 34 this.traits = form.traits || {}; 35 } 36 37 /** 38 * Ensure _form is updated when location-changed is emitted. 39 */ 40 _locationChangedPre(line, column) { 41 this._form.line = line; 42 this._form.column = column; 43 } 44 45 /** 46 * Return a new RuleModificationList or RuleRewriter for this node. 47 * A RuleRewriter will be returned when the rule's canSetRuleText 48 * trait is true; otherwise a RuleModificationList will be 49 * returned. 50 * 51 * @param {Window} win 52 * This is needed by the RuleRewriter. 53 * @param {CssPropertiesFront} cssProperties 54 * This is needed by the RuleRewriter. 55 * @return {RuleModificationList} 56 */ 57 startModifyingProperties(win, cssProperties) { 58 if (this.canSetRuleText) { 59 return new RuleRewriter( 60 win, 61 cssProperties.isKnown, 62 this, 63 this.authoredText 64 ); 65 } 66 return new RuleModificationList(this); 67 } 68 69 get type() { 70 return this._form.type; 71 } 72 get className() { 73 return this._form.className; 74 } 75 get line() { 76 return this._form.line || -1; 77 } 78 get column() { 79 return this._form.column || -1; 80 } 81 get cssText() { 82 return this._form.cssText; 83 } 84 get isNestedDeclarations() { 85 return !!this._form.isNestedDeclarations; 86 } 87 get authoredText() { 88 return typeof this._form.authoredText === "string" 89 ? this._form.authoredText 90 : this._form.cssText; 91 } 92 get declarations() { 93 return this._form.declarations || []; 94 } 95 get keyText() { 96 return this._form.keyText; 97 } 98 get name() { 99 return this._form.name; 100 } 101 get selectors() { 102 return this._form.selectors; 103 } 104 get selectorsSpecificity() { 105 return this._form.selectorsSpecificity; 106 } 107 108 /** 109 * Returns a concatenation of the rule's selector and all its ancestor "selectors". 110 * This is different from a "desugared" selector as what's returned is not an 111 * actual selector, but some kind of key that represent the rule selectors. 112 * This is used for the selector highlighter, where we need to know what's 113 * being highlighted. 114 * 115 * @returns {string} 116 */ 117 get computedSelector() { 118 let selector = ""; 119 for (const ancestor of this.ancestorData) { 120 let ancestorSelector; 121 if (ancestor.selectors) { 122 ancestorSelector = ancestor.selectors.join(","); 123 } else if (ancestor.type === "container") { 124 ancestorSelector = 125 ancestor.containerName + " " + ancestor.containerQuery; 126 } else if (ancestor.type === "supports") { 127 ancestorSelector = ancestor.conditionText; 128 } else if (ancestor.value) { 129 ancestorSelector = ancestor.value; 130 } 131 selector += 132 "/" + (ancestor.type ? ancestor.type + " " : "") + ancestorSelector; 133 } 134 135 return (selector ? selector + "/" : "") + this._form.selectors.join(","); 136 } 137 138 get selectorWarnings() { 139 return this._form.selectorWarnings; 140 } 141 142 get parentStyleSheet() { 143 const resourceCommand = this.targetFront.commands.resourceCommand; 144 return resourceCommand.getResourceById( 145 resourceCommand.TYPES.STYLESHEET, 146 this._form.parentStyleSheet 147 ); 148 } 149 150 get element() { 151 return this.conn.getFrontByID(this._form.element); 152 } 153 154 get href() { 155 if (this._form.href) { 156 return this._form.href; 157 } 158 const sheet = this.parentStyleSheet; 159 return sheet ? sheet.href : ""; 160 } 161 162 get nodeHref() { 163 const sheet = this.parentStyleSheet; 164 return sheet ? sheet.nodeHref : ""; 165 } 166 167 get canSetRuleText() { 168 return this._form.traits && this._form.traits.canSetRuleText; 169 } 170 171 get location() { 172 return { 173 source: this.parentStyleSheet, 174 href: this.href, 175 line: this.line, 176 column: this.column, 177 }; 178 } 179 180 get ancestorData() { 181 return this._form.ancestorData; 182 } 183 184 get userAdded() { 185 return this._form.userAdded; 186 } 187 188 async modifySelector(node, value) { 189 const response = await super.modifySelector( 190 node, 191 value, 192 this.canSetRuleText 193 ); 194 195 if (response.ruleProps) { 196 response.ruleProps = response.ruleProps.entries[0]; 197 } 198 return response; 199 } 200 201 setRuleText(newText, modifications) { 202 this._form.authoredText = newText; 203 return super.setRuleText(newText, modifications); 204 } 205 } 206 207 registerFront(StyleRuleFront); 208 209 /** 210 * Convenience API for building a list of attribute modifications 211 * for the `modifyProperties` request. A RuleModificationList holds a 212 * list of modifications that will be applied to a StyleRuleActor. 213 * The modifications are processed in the order in which they are 214 * added to the RuleModificationList. 215 * 216 * Objects of this type expose the same API as @see RuleRewriter. 217 * This lets the inspector use (mostly) the same code, regardless of 218 * whether the server implements setRuleText. 219 */ 220 class RuleModificationList { 221 /** 222 * Initialize a RuleModificationList. 223 * 224 * @param {StyleRuleFront} rule the associated rule 225 */ 226 constructor(rule) { 227 this.rule = rule; 228 this.modifications = []; 229 } 230 231 /** 232 * Apply the modifications in this object to the associated rule. 233 * 234 * @return {Promise} A promise which will be resolved when the modifications 235 * are complete; @see StyleRuleActor.modifyProperties. 236 */ 237 apply() { 238 return this.rule.modifyProperties(this.modifications); 239 } 240 241 /** 242 * Add a "set" entry to the modification list. 243 * 244 * @param {number} index index of the property in the rule. 245 * This can be -1 in the case where 246 * the rule does not support setRuleText; 247 * generally for setting properties 248 * on an element's style. 249 * @param {string} name the property's name 250 * @param {string} value the property's value 251 * @param {string} priority the property's priority, either the empty 252 * string or "important" 253 */ 254 setProperty(index, name, value, priority) { 255 this.modifications.push({ type: "set", index, name, value, priority }); 256 } 257 258 /** 259 * Add a "remove" entry to the modification list. 260 * 261 * @param {number} index index of the property in the rule. 262 * This can be -1 in the case where 263 * the rule does not support setRuleText; 264 * generally for setting properties 265 * on an element's style. 266 * @param {string} name the name of the property to remove 267 */ 268 removeProperty(index, name) { 269 this.modifications.push({ type: "remove", index, name }); 270 } 271 272 /** 273 * Rename a property. This implementation acts like 274 * |removeProperty|, because |setRuleText| is not available. 275 * 276 * @param {number} index index of the property in the rule. 277 * This can be -1 in the case where 278 * the rule does not support setRuleText; 279 * generally for setting properties 280 * on an element's style. 281 * @param {string} name current name of the property 282 * 283 * This parameter is also passed, but as it is not used in this 284 * implementation, it is omitted. It is documented here as this 285 * code also defined the interface implemented by @see RuleRewriter. 286 * @param {string} newName new name of the property 287 */ 288 renameProperty(index, name) { 289 this.removeProperty(index, name); 290 } 291 292 /** 293 * Enable or disable a property. This implementation acts like 294 * a no-op when enabling, because |setRuleText| is not available. 295 * 296 * @param {number} index index of the property in the rule. 297 * This can be -1 in the case where 298 * the rule does not support setRuleText; 299 * generally for setting properties 300 * on an element's style. 301 * @param {string} name current name of the property 302 * @param {boolean} isEnabled true if the property should be enabled; 303 * false if it should be disabled 304 */ 305 setPropertyEnabled(index, name, isEnabled) { 306 if (!isEnabled) { 307 this.modifications.push({ type: "disable", index, name }); 308 } 309 } 310 311 /** 312 * Create a new property. This implementation does nothing, because 313 * |setRuleText| is not available. 314 * 315 * These parameters are passed, but as they are not used in this 316 * implementation, they are omitted. They are documented here as 317 * this code also defined the interface implemented by @see 318 * RuleRewriter. 319 * 320 * @param {number} index index of the property in the rule. 321 * This can be -1 in the case where 322 * the rule does not support setRuleText; 323 * generally for setting properties 324 * on an element's style. 325 * @param {string} name name of the new property 326 * @param {string} value value of the new property 327 * @param {string} priority priority of the new property; either 328 * the empty string or "important" 329 * @param {boolean} enabled True if the new property should be 330 * enabled, false if disabled 331 */ 332 createProperty() { 333 // Nothing. 334 } 335 }