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/. */
11 } = require("devtools/shared/protocol");
15 } = require("devtools/shared/specs/styles");
16 const promise = require("promise");
18 loader.lazyRequireGetter(this, "RuleRewriter",
19 "devtools/shared/css/parsing-utils", true);
22 * PageStyleFront, the front object for the PageStyleActor
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();
30 form: function(form, detail) {
31 if (detail === "actorid") {
39 Front.prototype.destroy.call(this);
43 return this.inspector.walker;
46 get supportsAuthoredStyles() {
47 return this._form.traits && this._form.traits.authoredStyles;
50 get supportsFontStretchLevel4() {
51 return this._form.traits && this._form.traits.fontStretchLevel4;
54 get supportsFontStyleLevel4() {
55 return this._form.traits && this._form.traits.fontStyleLevel4;
58 get supportsFontVariations() {
59 return this._form.traits && this._form.traits.fontVariations;
62 get supportsFontWeightLevel4() {
63 return this._form.traits && this._form.traits.fontWeightLevel4;
66 getMatchedSelectors: custom(function(node, property, options) {
67 return this._getMatchedSelectors(node, property, options).then(ret => {
71 impl: "_getMatchedSelectors"
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);
82 const ret = await this._getApplied(node, options);
88 addNewRule: custom(function(node, pseudoClasses) {
90 if (this.supportsAuthoredStyles) {
91 addPromise = this._addNewRule(node, pseudoClasses, true);
93 addPromise = this._addNewRule(node, pseudoClasses);
95 return addPromise.then(ret => {
96 return ret.entries[0];
103 exports.PageStyleFront = PageStyleFront;
106 * StyleRuleFront, the front for the StyleRule actor.
108 const StyleRuleFront = FrontClassWithSpec(styleRuleSpec, {
109 initialize: function(client, form, ctx, detail) {
110 Front.prototype.initialize.call(this, client, form, ctx, detail);
113 destroy: function() {
114 Front.prototype.destroy.call(this);
117 form: function(form, detail) {
118 if (detail === "actorid") {
122 this.actorID = form.actor;
124 if (this._mediaText) {
125 this._mediaText = null;
130 * Ensure _form is updated when location-changed is emitted.
132 _locationChangedPre: preEvent("location-changed", function(line, column) {
133 this._clearOriginalLocation();
134 this._form.line = line;
135 this._form.column = column;
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
144 * @param {CssPropertiesFront} cssProperties
145 * This is needed by the RuleRewriter.
146 * @return {RuleModificationList}
148 startModifyingProperties: function(cssProperties) {
149 if (this.canSetRuleText) {
150 return new RuleRewriter(cssProperties.isKnown, this, this.authoredText);
152 return new RuleModificationList(this);
156 return this._form.type;
159 return this._form.line || -1;
162 return this._form.column || -1;
165 return this._form.cssText;
168 return this._form.authoredText || this._form.cssText;
171 return this._form.declarations || [];
174 return this._form.keyText;
177 return this._form.name;
180 return this._form.selectors;
183 return this._form.media;
186 if (!this._form.media) {
189 if (this._mediaText) {
190 return this._mediaText;
192 this._mediaText = this.media.join(", ");
193 return this._mediaText;
197 return this.conn.getActor(this._form.parentRule);
200 get parentStyleSheet() {
201 return this.conn.getActor(this._form.parentStyleSheet);
205 return this.conn.getActor(this._form.element);
209 if (this._form.href) {
210 return this._form.href;
212 const sheet = this.parentStyleSheet;
213 return sheet ? sheet.href : "";
217 const sheet = this.parentStyleSheet;
218 return sheet ? sheet.nodeHref : "";
221 get supportsModifySelectorUnmatched() {
222 return this._form.traits && this._form.traits.modifySelectorUnmatched;
225 get canSetRuleText() {
226 return this._form.traits && this._form.traits.canSetRuleText;
231 source: this.parentStyleSheet,
238 _clearOriginalLocation: function() {
239 this._originalLocation = null;
242 getOriginalLocation: function() {
243 if (this._originalLocation) {
244 return promise.resolve(this._originalLocation);
246 const parentSheet = this.parentStyleSheet;
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);
252 return parentSheet.getOriginalLocation(this.line, this.column)
253 .then(({ fromSourceMap, source, line, column }) => {
258 mediaText: this.mediaText
260 if (fromSourceMap === false) {
261 location.source = this.parentStyleSheet;
264 location.href = this.href;
266 this._originalLocation = location;
271 modifySelector: custom(async function(node, value) {
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);
278 response = await this.modifySelector2(node, value);
281 response = await this._modifySelector(value);
284 if (response.ruleProps) {
285 response.ruleProps = response.ruleProps.entries[0];
289 impl: "_modifySelector"
292 setRuleText: custom(function(newText) {
293 this._form.authoredText = newText;
294 return this._setRuleText(newText);
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.
313 class RuleModificationList {
315 * Initialize a RuleModificationList.
316 * @param {StyleRuleFront} rule the associated rule
320 this.modifications = [];
324 * Apply the modifications in this object to the associated rule.
326 * @return {Promise} A promise which will be resolved when the modifications
327 * are complete; @see StyleRuleActor.modifyProperties.
330 return this.rule.modifyProperties(this.modifications);
334 * Add a "set" entry to the modification list.
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"
346 setProperty(index, name, value, priority) {
347 this.modifications.push({
356 * Add a "remove" entry to the modification list.
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
365 removeProperty(index, name) {
366 this.modifications.push({
373 * Rename a property. This implementation acts like
374 * |removeProperty|, because |setRuleText| is not available.
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
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
388 renameProperty(index, name) {
389 this.removeProperty(index, name);
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.
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
406 setPropertyEnabled(index, name, isEnabled) {
408 this.removeProperty(index, name);
413 * Create a new property. This implementation does nothing, because
414 * |setRuleText| is not available.
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
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