diff options
Diffstat (limited to 'devtools/shared/fronts/styles.js')
-rw-r--r-- | devtools/shared/fronts/styles.js | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/devtools/shared/fronts/styles.js b/devtools/shared/fronts/styles.js new file mode 100644 index 000000000..116bb1f75 --- /dev/null +++ b/devtools/shared/fronts/styles.js @@ -0,0 +1,421 @@ +/* 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"; + +require("devtools/shared/fronts/stylesheets"); +const { + Front, + FrontClassWithSpec, + custom, + preEvent +} = require("devtools/shared/protocol"); +const { + pageStyleSpec, + styleRuleSpec +} = require("devtools/shared/specs/styles"); +const promise = require("promise"); +const { Task } = require("devtools/shared/task"); +const { Class } = require("sdk/core/heritage"); +const { RuleRewriter } = require("devtools/shared/css/parsing-utils"); + +/** + * PageStyleFront, the front object for the PageStyleActor + */ +const PageStyleFront = FrontClassWithSpec(pageStyleSpec, { + initialize: function (conn, form, ctx, detail) { + Front.prototype.initialize.call(this, conn, form, ctx, detail); + this.inspector = this.parent(); + }, + + form: function (form, detail) { + if (detail === "actorid") { + this.actorID = form; + return; + } + this._form = form; + }, + + destroy: function () { + Front.prototype.destroy.call(this); + }, + + get walker() { + return this.inspector.walker; + }, + + get supportsAuthoredStyles() { + return this._form.traits && this._form.traits.authoredStyles; + }, + + getMatchedSelectors: custom(function (node, property, options) { + return this._getMatchedSelectors(node, property, options).then(ret => { + return ret.matched; + }); + }, { + impl: "_getMatchedSelectors" + }), + + getApplied: custom(Task.async(function* (node, options = {}) { + // If the getApplied method doesn't recreate the style cache itself, this + // means a call to cssLogic.highlight is required before trying to access + // the applied rules. Issue a request to getLayout if this is the case. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1103993#c16. + if (!this._form.traits || !this._form.traits.getAppliedCreatesStyleCache) { + yield this.getLayout(node); + } + let ret = yield this._getApplied(node, options); + return ret.entries; + }), { + impl: "_getApplied" + }), + + addNewRule: custom(function (node, pseudoClasses) { + let addPromise; + if (this.supportsAuthoredStyles) { + addPromise = this._addNewRule(node, pseudoClasses, true); + } else { + addPromise = this._addNewRule(node, pseudoClasses); + } + return addPromise.then(ret => { + return ret.entries[0]; + }); + }, { + impl: "_addNewRule" + }) +}); + +exports.PageStyleFront = PageStyleFront; + +/** + * StyleRuleFront, the front for the StyleRule actor. + */ +const StyleRuleFront = FrontClassWithSpec(styleRuleSpec, { + initialize: function (client, form, ctx, detail) { + Front.prototype.initialize.call(this, client, form, ctx, detail); + }, + + destroy: function () { + Front.prototype.destroy.call(this); + }, + + form: function (form, detail) { + if (detail === "actorid") { + this.actorID = form; + return; + } + this.actorID = form.actor; + this._form = form; + if (this._mediaText) { + this._mediaText = null; + } + }, + + /** + * Ensure _form is updated when location-changed is emitted. + */ + _locationChangedPre: preEvent("location-changed", function (line, column) { + this._clearOriginalLocation(); + this._form.line = line; + this._form.column = column; + }), + + /** + * Return a new RuleModificationList or RuleRewriter for this node. + * A RuleRewriter will be returned when the rule's canSetRuleText + * trait is true; otherwise a RuleModificationList will be + * returned. + * + * @param {CssPropertiesFront} cssProperties + * This is needed by the RuleRewriter. + * @return {RuleModificationList} + */ + startModifyingProperties: function (cssProperties) { + if (this.canSetRuleText) { + return new RuleRewriter(cssProperties.isKnown, this, this.authoredText); + } + return new RuleModificationList(this); + }, + + get type() { + return this._form.type; + }, + get line() { + return this._form.line || -1; + }, + get column() { + return this._form.column || -1; + }, + get cssText() { + return this._form.cssText; + }, + get authoredText() { + return this._form.authoredText || this._form.cssText; + }, + get declarations() { + return this._form.declarations || []; + }, + get keyText() { + return this._form.keyText; + }, + get name() { + return this._form.name; + }, + get selectors() { + return this._form.selectors; + }, + get media() { + return this._form.media; + }, + get mediaText() { + if (!this._form.media) { + return null; + } + if (this._mediaText) { + return this._mediaText; + } + this._mediaText = this.media.join(", "); + return this._mediaText; + }, + + get parentRule() { + return this.conn.getActor(this._form.parentRule); + }, + + get parentStyleSheet() { + return this.conn.getActor(this._form.parentStyleSheet); + }, + + get element() { + return this.conn.getActor(this._form.element); + }, + + get href() { + if (this._form.href) { + return this._form.href; + } + let sheet = this.parentStyleSheet; + return sheet ? sheet.href : ""; + }, + + get nodeHref() { + let sheet = this.parentStyleSheet; + return sheet ? sheet.nodeHref : ""; + }, + + get supportsModifySelectorUnmatched() { + return this._form.traits && this._form.traits.modifySelectorUnmatched; + }, + + get canSetRuleText() { + return this._form.traits && this._form.traits.canSetRuleText; + }, + + get location() { + return { + source: this.parentStyleSheet, + href: this.href, + line: this.line, + column: this.column + }; + }, + + _clearOriginalLocation: function () { + this._originalLocation = null; + }, + + getOriginalLocation: function () { + if (this._originalLocation) { + return promise.resolve(this._originalLocation); + } + let parentSheet = this.parentStyleSheet; + if (!parentSheet) { + // This rule doesn't belong to a stylesheet so it is an inline style. + // Inline styles do not have any mediaText so we can return early. + return promise.resolve(this.location); + } + return parentSheet.getOriginalLocation(this.line, this.column) + .then(({ fromSourceMap, source, line, column }) => { + let location = { + href: source, + line: line, + column: column, + mediaText: this.mediaText + }; + if (fromSourceMap === false) { + location.source = this.parentStyleSheet; + } + if (!source) { + location.href = this.href; + } + this._originalLocation = location; + return location; + }); + }, + + modifySelector: custom(Task.async(function* (node, value) { + let response; + if (this.supportsModifySelectorUnmatched) { + // If the debugee supports adding unmatched rules (post FF41) + if (this.canSetRuleText) { + response = yield this.modifySelector2(node, value, true); + } else { + response = yield this.modifySelector2(node, value); + } + } else { + response = yield this._modifySelector(value); + } + + if (response.ruleProps) { + response.ruleProps = response.ruleProps.entries[0]; + } + return response; + }), { + impl: "_modifySelector" + }), + + setRuleText: custom(function (newText) { + this._form.authoredText = newText; + return this._setRuleText(newText); + }, { + impl: "_setRuleText" + }) +}); + +exports.StyleRuleFront = StyleRuleFront; + +/** + * Convenience API for building a list of attribute modifications + * for the `modifyProperties` request. A RuleModificationList holds a + * list of modifications that will be applied to a StyleRuleActor. + * The modifications are processed in the order in which they are + * added to the RuleModificationList. + * + * Objects of this type expose the same API as @see RuleRewriter. + * This lets the inspector use (mostly) the same code, regardless of + * whether the server implements setRuleText. + */ +var RuleModificationList = Class({ + /** + * Initialize a RuleModificationList. + * @param {StyleRuleFront} rule the associated rule + */ + initialize: function (rule) { + this.rule = rule; + this.modifications = []; + }, + + /** + * Apply the modifications in this object to the associated rule. + * + * @return {Promise} A promise which will be resolved when the modifications + * are complete; @see StyleRuleActor.modifyProperties. + */ + apply: function () { + return this.rule.modifyProperties(this.modifications); + }, + + /** + * Add a "set" entry to the modification list. + * + * @param {Number} index index of the property in the rule. + * This can be -1 in the case where + * the rule does not support setRuleText; + * generally for setting properties + * on an element's style. + * @param {String} name the property's name + * @param {String} value the property's value + * @param {String} priority the property's priority, either the empty + * string or "important" + */ + setProperty: function (index, name, value, priority) { + this.modifications.push({ + type: "set", + name: name, + value: value, + priority: priority + }); + }, + + /** + * Add a "remove" entry to the modification list. + * + * @param {Number} index index of the property in the rule. + * This can be -1 in the case where + * the rule does not support setRuleText; + * generally for setting properties + * on an element's style. + * @param {String} name the name of the property to remove + */ + removeProperty: function (index, name) { + this.modifications.push({ + type: "remove", + name: name + }); + }, + + /** + * Rename a property. This implementation acts like + * |removeProperty|, because |setRuleText| is not available. + * + * @param {Number} index index of the property in the rule. + * This can be -1 in the case where + * the rule does not support setRuleText; + * generally for setting properties + * on an element's style. + * @param {String} name current name of the property + * + * This parameter is also passed, but as it is not used in this + * implementation, it is omitted. It is documented here as this + * code also defined the interface implemented by @see RuleRewriter. + * @param {String} newName new name of the property + */ + renameProperty: function (index, name) { + this.removeProperty(index, name); + }, + + /** + * Enable or disable a property. This implementation acts like + * |removeProperty| when disabling, or a no-op when enabling, + * because |setRuleText| is not available. + * + * @param {Number} index index of the property in the rule. + * This can be -1 in the case where + * the rule does not support setRuleText; + * generally for setting properties + * on an element's style. + * @param {String} name current name of the property + * @param {Boolean} isEnabled true if the property should be enabled; + * false if it should be disabled + */ + setPropertyEnabled: function (index, name, isEnabled) { + if (!isEnabled) { + this.removeProperty(index, name); + } + }, + + /** + * Create a new property. This implementation does nothing, because + * |setRuleText| is not available. + * + * These parameters are passed, but as they are not used in this + * implementation, they are omitted. They are documented here as + * this code also defined the interface implemented by @see + * RuleRewriter. + * + * @param {Number} index index of the property in the rule. + * This can be -1 in the case where + * the rule does not support setRuleText; + * generally for setting properties + * on an element's style. + * @param {String} name name of the new property + * @param {String} value value of the new property + * @param {String} priority priority of the new property; either + * the empty string or "important" + * @param {Boolean} enabled True if the new property should be + * enabled, false if disabled + */ + createProperty: function () { + // Nothing. + }, +}); |