summaryrefslogtreecommitdiffstats
path: root/devtools/shared/fronts/styles.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/fronts/styles.js')
-rw-r--r--devtools/shared/fronts/styles.js421
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.
+ },
+});