Bug 1467571 [wpt PR 11385] - Make manifest's parsers quicker, a=testonly
[gecko.git] / devtools / shared / fronts / styles.js
blob82ae4221cde1de01aa45b07ea703c99fa2a05cfb
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 "use strict";
6 const {
7   Front,
8   FrontClassWithSpec,
9   custom,
10   preEvent
11 } = require("devtools/shared/protocol");
12 const {
13   pageStyleSpec,
14   styleRuleSpec
15 } = require("devtools/shared/specs/styles");
16 const promise = require("promise");
18 loader.lazyRequireGetter(this, "RuleRewriter",
19   "devtools/shared/css/parsing-utils", true);
21 /**
22  * PageStyleFront, the front object for the PageStyleActor
23  */
24 const PageStyleFront = FrontClassWithSpec(pageStyleSpec, {
25   initialize: function(conn, form, ctx, detail) {
26     Front.prototype.initialize.call(this, conn, form, ctx, detail);
27     this.inspector = this.parent();
28   },
30   form: function(form, detail) {
31     if (detail === "actorid") {
32       this.actorID = form;
33       return;
34     }
35     this._form = form;
36   },
38   destroy: function() {
39     Front.prototype.destroy.call(this);
40   },
42   get walker() {
43     return this.inspector.walker;
44   },
46   get supportsAuthoredStyles() {
47     return this._form.traits && this._form.traits.authoredStyles;
48   },
50   get supportsFontStretchLevel4() {
51     return this._form.traits && this._form.traits.fontStretchLevel4;
52   },
54   get supportsFontStyleLevel4() {
55     return this._form.traits && this._form.traits.fontStyleLevel4;
56   },
58   get supportsFontVariations() {
59     return this._form.traits && this._form.traits.fontVariations;
60   },
62   get supportsFontWeightLevel4() {
63     return this._form.traits && this._form.traits.fontWeightLevel4;
64   },
66   getMatchedSelectors: custom(function(node, property, options) {
67     return this._getMatchedSelectors(node, property, options).then(ret => {
68       return ret.matched;
69     });
70   }, {
71     impl: "_getMatchedSelectors"
72   }),
74   getApplied: custom(async function(node, options = {}) {
75     // If the getApplied method doesn't recreate the style cache itself, this
76     // means a call to cssLogic.highlight is required before trying to access
77     // the applied rules. Issue a request to getLayout if this is the case.
78     // See https://bugzilla.mozilla.org/show_bug.cgi?id=1103993#c16.
79     if (!this._form.traits || !this._form.traits.getAppliedCreatesStyleCache) {
80       await this.getLayout(node);
81     }
82     const ret = await this._getApplied(node, options);
83     return ret.entries;
84   }, {
85     impl: "_getApplied"
86   }),
88   addNewRule: custom(function(node, pseudoClasses) {
89     let addPromise;
90     if (this.supportsAuthoredStyles) {
91       addPromise = this._addNewRule(node, pseudoClasses, true);
92     } else {
93       addPromise = this._addNewRule(node, pseudoClasses);
94     }
95     return addPromise.then(ret => {
96       return ret.entries[0];
97     });
98   }, {
99     impl: "_addNewRule"
100   })
103 exports.PageStyleFront = PageStyleFront;
106  * StyleRuleFront, the front for the StyleRule actor.
107  */
108 const StyleRuleFront = FrontClassWithSpec(styleRuleSpec, {
109   initialize: function(client, form, ctx, detail) {
110     Front.prototype.initialize.call(this, client, form, ctx, detail);
111   },
113   destroy: function() {
114     Front.prototype.destroy.call(this);
115   },
117   form: function(form, detail) {
118     if (detail === "actorid") {
119       this.actorID = form;
120       return;
121     }
122     this.actorID = form.actor;
123     this._form = form;
124     if (this._mediaText) {
125       this._mediaText = null;
126     }
127   },
129   /**
130    * Ensure _form is updated when location-changed is emitted.
131    */
132   _locationChangedPre: preEvent("location-changed", function(line, column) {
133     this._clearOriginalLocation();
134     this._form.line = line;
135     this._form.column = column;
136   }),
138   /**
139    * Return a new RuleModificationList or RuleRewriter for this node.
140    * A RuleRewriter will be returned when the rule's canSetRuleText
141    * trait is true; otherwise a RuleModificationList will be
142    * returned.
143    *
144    * @param {CssPropertiesFront} cssProperties
145    *                             This is needed by the RuleRewriter.
146    * @return {RuleModificationList}
147    */
148   startModifyingProperties: function(cssProperties) {
149     if (this.canSetRuleText) {
150       return new RuleRewriter(cssProperties.isKnown, this, this.authoredText);
151     }
152     return new RuleModificationList(this);
153   },
155   get type() {
156     return this._form.type;
157   },
158   get line() {
159     return this._form.line || -1;
160   },
161   get column() {
162     return this._form.column || -1;
163   },
164   get cssText() {
165     return this._form.cssText;
166   },
167   get authoredText() {
168     return this._form.authoredText || this._form.cssText;
169   },
170   get declarations() {
171     return this._form.declarations || [];
172   },
173   get keyText() {
174     return this._form.keyText;
175   },
176   get name() {
177     return this._form.name;
178   },
179   get selectors() {
180     return this._form.selectors;
181   },
182   get media() {
183     return this._form.media;
184   },
185   get mediaText() {
186     if (!this._form.media) {
187       return null;
188     }
189     if (this._mediaText) {
190       return this._mediaText;
191     }
192     this._mediaText = this.media.join(", ");
193     return this._mediaText;
194   },
196   get parentRule() {
197     return this.conn.getActor(this._form.parentRule);
198   },
200   get parentStyleSheet() {
201     return this.conn.getActor(this._form.parentStyleSheet);
202   },
204   get element() {
205     return this.conn.getActor(this._form.element);
206   },
208   get href() {
209     if (this._form.href) {
210       return this._form.href;
211     }
212     const sheet = this.parentStyleSheet;
213     return sheet ? sheet.href : "";
214   },
216   get nodeHref() {
217     const sheet = this.parentStyleSheet;
218     return sheet ? sheet.nodeHref : "";
219   },
221   get supportsModifySelectorUnmatched() {
222     return this._form.traits && this._form.traits.modifySelectorUnmatched;
223   },
225   get canSetRuleText() {
226     return this._form.traits && this._form.traits.canSetRuleText;
227   },
229   get location() {
230     return {
231       source: this.parentStyleSheet,
232       href: this.href,
233       line: this.line,
234       column: this.column
235     };
236   },
238   _clearOriginalLocation: function() {
239     this._originalLocation = null;
240   },
242   getOriginalLocation: function() {
243     if (this._originalLocation) {
244       return promise.resolve(this._originalLocation);
245     }
246     const parentSheet = this.parentStyleSheet;
247     if (!parentSheet) {
248       // This rule doesn't belong to a stylesheet so it is an inline style.
249       // Inline styles do not have any mediaText so we can return early.
250       return promise.resolve(this.location);
251     }
252     return parentSheet.getOriginalLocation(this.line, this.column)
253       .then(({ fromSourceMap, source, line, column }) => {
254         const location = {
255           href: source,
256           line: line,
257           column: column,
258           mediaText: this.mediaText
259         };
260         if (fromSourceMap === false) {
261           location.source = this.parentStyleSheet;
262         }
263         if (!source) {
264           location.href = this.href;
265         }
266         this._originalLocation = location;
267         return location;
268       });
269   },
271   modifySelector: custom(async function(node, value) {
272     let response;
273     if (this.supportsModifySelectorUnmatched) {
274       // If the debugee supports adding unmatched rules (post FF41)
275       if (this.canSetRuleText) {
276         response = await this.modifySelector2(node, value, true);
277       } else {
278         response = await this.modifySelector2(node, value);
279       }
280     } else {
281       response = await this._modifySelector(value);
282     }
284     if (response.ruleProps) {
285       response.ruleProps = response.ruleProps.entries[0];
286     }
287     return response;
288   }, {
289     impl: "_modifySelector"
290   }),
292   setRuleText: custom(function(newText) {
293     this._form.authoredText = newText;
294     return this._setRuleText(newText);
295   }, {
296     impl: "_setRuleText"
297   })
300 exports.StyleRuleFront = StyleRuleFront;
303  * Convenience API for building a list of attribute modifications
304  * for the `modifyProperties` request.  A RuleModificationList holds a
305  * list of modifications that will be applied to a StyleRuleActor.
306  * The modifications are processed in the order in which they are
307  * added to the RuleModificationList.
309  * Objects of this type expose the same API as @see RuleRewriter.
310  * This lets the inspector use (mostly) the same code, regardless of
311  * whether the server implements setRuleText.
312  */
313 class RuleModificationList {
314   /**
315    * Initialize a RuleModificationList.
316    * @param {StyleRuleFront} rule the associated rule
317    */
318   constructor(rule) {
319     this.rule = rule;
320     this.modifications = [];
321   }
323   /**
324    * Apply the modifications in this object to the associated rule.
325    *
326    * @return {Promise} A promise which will be resolved when the modifications
327    *         are complete; @see StyleRuleActor.modifyProperties.
328    */
329   apply() {
330     return this.rule.modifyProperties(this.modifications);
331   }
333   /**
334    * Add a "set" entry to the modification list.
335    *
336    * @param {Number} index index of the property in the rule.
337    *                       This can be -1 in the case where
338    *                       the rule does not support setRuleText;
339    *                       generally for setting properties
340    *                       on an element's style.
341    * @param {String} name the property's name
342    * @param {String} value the property's value
343    * @param {String} priority the property's priority, either the empty
344    *                          string or "important"
345    */
346   setProperty(index, name, value, priority) {
347     this.modifications.push({
348       type: "set",
349       name: name,
350       value: value,
351       priority: priority
352     });
353   }
355   /**
356    * Add a "remove" entry to the modification list.
357    *
358    * @param {Number} index index of the property in the rule.
359    *                       This can be -1 in the case where
360    *                       the rule does not support setRuleText;
361    *                       generally for setting properties
362    *                       on an element's style.
363    * @param {String} name the name of the property to remove
364    */
365   removeProperty(index, name) {
366     this.modifications.push({
367       type: "remove",
368       name: name
369     });
370   }
372   /**
373    * Rename a property.  This implementation acts like
374    * |removeProperty|, because |setRuleText| is not available.
375    *
376    * @param {Number} index index of the property in the rule.
377    *                       This can be -1 in the case where
378    *                       the rule does not support setRuleText;
379    *                       generally for setting properties
380    *                       on an element's style.
381    * @param {String} name current name of the property
382    *
383    * This parameter is also passed, but as it is not used in this
384    * implementation, it is omitted.  It is documented here as this
385    * code also defined the interface implemented by @see RuleRewriter.
386    * @param {String} newName new name of the property
387    */
388   renameProperty(index, name) {
389     this.removeProperty(index, name);
390   }
392   /**
393    * Enable or disable a property.  This implementation acts like
394    * |removeProperty| when disabling, or a no-op when enabling,
395    * because |setRuleText| is not available.
396    *
397    * @param {Number} index index of the property in the rule.
398    *                       This can be -1 in the case where
399    *                       the rule does not support setRuleText;
400    *                       generally for setting properties
401    *                       on an element's style.
402    * @param {String} name current name of the property
403    * @param {Boolean} isEnabled true if the property should be enabled;
404    *                        false if it should be disabled
405    */
406   setPropertyEnabled(index, name, isEnabled) {
407     if (!isEnabled) {
408       this.removeProperty(index, name);
409     }
410   }
412   /**
413    * Create a new property.  This implementation does nothing, because
414    * |setRuleText| is not available.
415    *
416    * These parameters are passed, but as they are not used in this
417    * implementation, they are omitted.  They are documented here as
418    * this code also defined the interface implemented by @see
419    * RuleRewriter.
420    *
421    * @param {Number} index index of the property in the rule.
422    *                       This can be -1 in the case where
423    *                       the rule does not support setRuleText;
424    *                       generally for setting properties
425    *                       on an element's style.
426    * @param {String} name name of the new property
427    * @param {String} value value of the new property
428    * @param {String} priority priority of the new property; either
429    *                          the empty string or "important"
430    * @param {Boolean} enabled True if the new property should be
431    *                          enabled, false if disabled
432    */
433   createProperty() {
434     // Nothing.
435   }