190 lines
5.9 KiB
JavaScript
190 lines
5.9 KiB
JavaScript
/* 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.panelWin,
|
|
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.panelWin,
|
|
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;
|