summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/boxmodel/utils/editing-session.js
blob: 3175375f22d1f79707be55578feb9348a0756b64 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/* 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";

/**
 * An instance of EditingSession tracks changes that have been made during the
 * modification of box model values. All of these changes can be reverted by
 * calling revert.
 *
 * @param  {InspectorPanel} inspector
 *         The inspector panel.
 * @param  {Document} doc
 *         A DOM document that can be used to test style rules.
 * @param  {Array} rules
 *         An array of the style rules defined for the node being
 *         edited. These should be in order of priority, least
 *         important first.
 */
function EditingSession({ inspector, doc, elementRules }) {
  this._doc = doc;
  this._inspector = inspector;
  this._rules = elementRules;
  this._modifications = new Map();
}

EditingSession.prototype = {
  /**
   * Gets the value of a single property from the CSS rule.
   *
   * @param  {StyleRuleFront} rule
   *         The CSS rule.
   * @param  {String} property
   *         The name of the property.
   * @return {String} the value.
   */
  getPropertyFromRule(rule, property) {
    // Use the parsed declarations in the StyleRuleFront object if available.
    const index = this.getPropertyIndex(property, rule);
    if (index !== -1) {
      return rule.declarations[index].value;
    }

    // Fallback to parsing the cssText locally otherwise.
    const dummyStyle = this._element.style;
    dummyStyle.cssText = rule.cssText;
    return dummyStyle.getPropertyValue(property);
  },

  /**
   * Returns the current value for a property as a string or the empty string if
   * no style rules affect the property.
   *
   * @param  {String} property
   *         The name of the property as a string
   */
  getProperty(property) {
    // Create a hidden element for getPropertyFromRule to use
    const div = this._doc.createElement("div");
    div.setAttribute("style", "display: none");
    this._doc.getElementById("inspector-main-content").appendChild(div);
    this._element = this._doc.createElement("p");
    div.appendChild(this._element);

    // As the rules are in order of priority we can just iterate until we find
    // the first that defines a value for the property and return that.
    for (const rule of this._rules) {
      const value = this.getPropertyFromRule(rule, property);
      if (value !== "") {
        div.remove();
        return value;
      }
    }
    div.remove();
    return "";
  },

  /**
   * Get the index of a given css property name in a CSS rule.
   * Or -1, if there are no properties in the rule yet.
   *
   * @param  {String} name
   *         The property name.
   * @param  {StyleRuleFront} rule
   *         Optional, defaults to the element style rule.
   * @return {Number} The property index in the rule.
   */
  getPropertyIndex(name, rule = this._rules[0]) {
    if (!rule.declarations.length) {
      return -1;
    }

    return rule.declarations.findIndex(p => p.name === name);
  },

  /**
   * Sets a number of properties on the node.
   *
   * @param  {Array} properties
   *         An array of properties, each is an object with name and
   *         value properties. If the value is "" then the property
   *         is removed.
   * @return {Promise} Resolves when the modifications are complete.
   */
  async setProperties(properties) {
    for (const property of properties) {
      // Get a RuleModificationList or RuleRewriter helper object from the
      // StyleRuleActor to make changes to CSS properties.
      // Note that RuleRewriter doesn't support modifying several properties at
      // once, so we do this in a sequence here.
      const modifications = this._rules[0].startModifyingProperties(
        this._inspector.cssProperties
      );

      // Remember the property so it can be reverted.
      if (!this._modifications.has(property.name)) {
        this._modifications.set(
          property.name,
          this.getPropertyFromRule(this._rules[0], property.name)
        );
      }

      // Find the index of the property to be changed, or get the next index to
      // insert the new property at.
      let index = this.getPropertyIndex(property.name);
      if (index === -1) {
        index = this._rules[0].declarations.length;
      }

      if (property.value == "") {
        modifications.removeProperty(index, property.name);
      } else {
        modifications.setProperty(index, property.name, property.value, "");
      }

      await modifications.apply();
    }
  },

  /**
   * Reverts all of the property changes made by this instance.
   *
   * @return {Promise} Resolves when all properties have been reverted.
   */
  async revert() {
    // Revert each property that we modified previously, one by one. See
    // setProperties for information about why.
    for (const [property, value] of this._modifications) {
      const modifications = this._rules[0].startModifyingProperties(
        this._inspector.cssProperties
      );

      // Find the index of the property to be reverted.
      let index = this.getPropertyIndex(property);

      if (value != "") {
        // If the property doesn't exist anymore, insert at the beginning of the
        // rule.
        if (index === -1) {
          index = 0;
        }
        modifications.setProperty(index, property, value, "");
      } else {
        // If the property doesn't exist anymore, no need to remove it. It had
        // not been added after all.
        if (index === -1) {
          continue;
        }
        modifications.removeProperty(index, property);
      }

      await modifications.apply();
    }
  },

  destroy() {
    this._modifications.clear();

    this._cssProperties = null;
    this._doc = null;
    this._inspector = null;
    this._modifications = null;
    this._rules = null;
  },
};

module.exports = EditingSession;