зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1499049
- (Part 3) Add new reducer logic for tracking CSS changes to nested rules; r=pbro
Depends on D8719 - Add methods to generate unique identifiers for stylesheets and CSS rules changed within those stylesheets. These are used as IDs in the Redux store; - Add logic to generate entries in the store for each one of the rule's ancestors and assign parent/child dependencies. This single-level structure for all rules in a source helps with quickly identifying a rule on subsequent changes independent of its rule tree (it avoids needless tree traversal). The parent/child references help with rendering of the nested rule structure in the Changes panel; - Deep clone Redux store state before aggregating tracked changes (no more mutations of previous state). Differential Revision: https://phabricator.services.mozilla.com/D8720 --HG-- rename : devtools/client/inspector/changes/moz.build => devtools/client/inspector/changes/utils/moz.build extra : moz-landing-system : lando
This commit is contained in:
Родитель
4c3717a22f
Коммит
da9c192a4c
|
@ -17,10 +17,10 @@ module.exports = {
|
|||
};
|
||||
},
|
||||
|
||||
trackChange(data) {
|
||||
trackChange(change) {
|
||||
return {
|
||||
type: TRACK_CHANGE,
|
||||
data,
|
||||
change,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ DIRS += [
|
|||
'actions',
|
||||
'components',
|
||||
'reducers',
|
||||
'utils',
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
|
|
|
@ -4,16 +4,182 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { getSourceHash, getRuleHash } = require("../utils/changes-utils");
|
||||
|
||||
const {
|
||||
RESET_CHANGES,
|
||||
TRACK_CHANGE,
|
||||
} = require("../actions/index");
|
||||
|
||||
/**
|
||||
* Return a deep clone of the given state object.
|
||||
*
|
||||
* @param {Object} state
|
||||
* @return {Object}
|
||||
*/
|
||||
function cloneState(state = {}) {
|
||||
return Object.entries(state).reduce((sources, [sourceId, source]) => {
|
||||
sources[sourceId] = {
|
||||
...source,
|
||||
rules: Object.entries(source.rules).reduce((rules, [ruleId, rule]) => {
|
||||
rules[ruleId] = {
|
||||
...rule,
|
||||
children: rule.children.slice(0),
|
||||
add: { ...rule.add },
|
||||
remove: { ...rule.remove },
|
||||
};
|
||||
|
||||
return rules;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
return sources;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given information about a CSS rule and its ancestor rules (@media, @supports, etc),
|
||||
* create entries in the given rules collection for each rule and assign parent/child
|
||||
* dependencies.
|
||||
*
|
||||
* @param {Object} ruleData
|
||||
* Information about a CSS rule:
|
||||
* {
|
||||
* selector: {String}
|
||||
* CSS selector text
|
||||
* ancestors: {Array}
|
||||
* Flattened CSS rule tree of the rule's ancestors with the root rule
|
||||
* at the beginning of the array and the leaf rule at the end.
|
||||
* ruleIndex: {Array}
|
||||
* Indexes of each ancestor rule within its parent rule.
|
||||
* }
|
||||
*
|
||||
* @param {Object} rules
|
||||
* Collection of rules to be mutated.
|
||||
* This is a reference to the corresponding `rules` object from the state.
|
||||
*
|
||||
* @return {Object}
|
||||
* Entry for the CSS rule created the given collection of rules.
|
||||
*/
|
||||
function createRule(ruleData, rules) {
|
||||
// Append the rule data to the flattened CSS rule tree with its ancestors.
|
||||
const ruleAncestry = [...ruleData.ancestors, { ...ruleData }];
|
||||
|
||||
return ruleAncestry
|
||||
// First, generate a unique identifier for each rule.
|
||||
.map((rule, index) => {
|
||||
// Ensure each rule has ancestors excluding itself (expand the flattened rule tree).
|
||||
rule.ancestors = ruleAncestry.slice(0, index);
|
||||
// Ensure each rule has a selector text.
|
||||
// For the purpose of displaying in the UI, we treat at-rules as selectors.
|
||||
if (!rule.selector) {
|
||||
rule.selector =
|
||||
`${rule.typeName} ${(rule.conditionText || rule.name || rule.keyText)}`;
|
||||
}
|
||||
|
||||
return getRuleHash(rule);
|
||||
})
|
||||
// Then, create new entries in the rules collection and assign dependencies.
|
||||
.map((ruleId, index, array) => {
|
||||
const { selector } = ruleAncestry[index];
|
||||
const prevRuleId = array[index - 1];
|
||||
const nextRuleId = array[index + 1];
|
||||
|
||||
// Copy or create an entry for this rule.
|
||||
rules[ruleId] = Object.assign({}, { selector, children: [] }, rules[ruleId]);
|
||||
|
||||
// The next ruleId is lower in the rule tree, therefore it's a child of this rule.
|
||||
if (nextRuleId && !rules[ruleId].children.includes(nextRuleId)) {
|
||||
rules[ruleId].children.push(nextRuleId);
|
||||
}
|
||||
|
||||
// The previous ruleId is higher in the rule tree, therefore it's the parent.
|
||||
if (prevRuleId) {
|
||||
rules[ruleId].parent = prevRuleId;
|
||||
}
|
||||
|
||||
return rules[ruleId];
|
||||
})
|
||||
// Finally, return the last rule in the array which is the rule we set out to create.
|
||||
.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregated changes grouped by sources (stylesheet/element), which contain rules,
|
||||
* which contain collections of added and removed CSS declarations.
|
||||
*
|
||||
* Structure:
|
||||
* <sourceId>: {
|
||||
* type: // "stylesheet" or "element"
|
||||
* href: // Stylesheet or document URL
|
||||
* rules: {
|
||||
* <ruleId>: {
|
||||
* selector: "" // String CSS selector or CSS at-rule text
|
||||
* children: [] // Array of <ruleId> for child rules of this rule.
|
||||
* parent: // <ruleId> of the parent rule
|
||||
* add: {
|
||||
* <property>: <value> // CSS declaration
|
||||
* ...
|
||||
* },
|
||||
* remove: {
|
||||
* <property>: <value> // CSS declaration
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
* ... // more rules
|
||||
* }
|
||||
* }
|
||||
* ... // more sources
|
||||
*/
|
||||
const INITIAL_STATE = {};
|
||||
|
||||
const reducers = {
|
||||
|
||||
[TRACK_CHANGE](state, { data }) {
|
||||
[TRACK_CHANGE](state, { change }) {
|
||||
const defaults = {
|
||||
selector: null,
|
||||
source: {},
|
||||
ancestors: [],
|
||||
add: {},
|
||||
remove: {},
|
||||
};
|
||||
|
||||
change = { ...defaults, ...change };
|
||||
state = cloneState(state);
|
||||
|
||||
const { type, href, index } = change.source;
|
||||
const { selector, ancestors, ruleIndex } = change;
|
||||
const sourceId = getSourceHash(change.source);
|
||||
const ruleId = getRuleHash({ selector, ancestors, ruleIndex });
|
||||
|
||||
// Copy or create object identifying the source (styelsheet/element) for this change.
|
||||
const source = Object.assign({}, state[sourceId], { type, href, index });
|
||||
// Copy or create collection of all rules ever changed in this source.
|
||||
const rules = Object.assign({}, source.rules);
|
||||
// Refrence or create object identifying the rule for this change.
|
||||
let rule = rules[ruleId];
|
||||
if (!rule) {
|
||||
rule = createRule({ selector, ancestors, ruleIndex }, rules);
|
||||
}
|
||||
// Copy or create collection of all CSS declarations ever added to this rule.
|
||||
const add = Object.assign({}, rule.add);
|
||||
// Copy or create collection of all CSS declarations ever removed from this rule.
|
||||
const remove = Object.assign({}, rule.remove);
|
||||
|
||||
// Track the remove operation only if the property was not previously introduced by
|
||||
// an add operation. This ensures repeated changes of the same property register as
|
||||
// a single remove operation of its original value.
|
||||
if (change.remove && change.remove.property && !add[change.remove.property]) {
|
||||
remove[change.remove.property] = change.remove.value;
|
||||
}
|
||||
|
||||
if (change.add && change.add.property) {
|
||||
add[change.add.property] = change.add.value;
|
||||
}
|
||||
|
||||
source.rules = { ...rules, [ruleId]: { ...rule, add, remove } };
|
||||
state[sourceId] = source;
|
||||
|
||||
return state;
|
||||
},
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* Generate a hash that uniquely identifies a stylesheet or element style attribute.
|
||||
*
|
||||
* @param {Object} source
|
||||
* Information about a stylesheet or element style attribute:
|
||||
* {
|
||||
* type: {String}
|
||||
* One of "stylesheet" or "element".
|
||||
* index: {Number|String}
|
||||
* Position of the styleshet in the list of stylesheets in the document.
|
||||
* If `type` is "element", `index` is the generated selector which
|
||||
* uniquely identifies the element in the document.
|
||||
* href: {String|null}
|
||||
* URL of the stylesheet or of the document when `type` is "element".
|
||||
* If the stylesheet is inline, `href` is null.
|
||||
* }
|
||||
* @return {String}
|
||||
*/
|
||||
function getSourceHash(source) {
|
||||
const { type, index, href = "inline" } = source;
|
||||
|
||||
return `${type}${index}${href}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a hash that uniquely identifies a CSS rule.
|
||||
*
|
||||
* @param {Object} ruleData
|
||||
* Information about a CSS rule:
|
||||
* {
|
||||
* selector: {String}
|
||||
* CSS selector text
|
||||
* ancestors: {Array}
|
||||
* Flattened CSS rule tree of the rule's ancestors with the root rule
|
||||
* at the beginning of the array and the leaf rule at the end.
|
||||
* ruleIndex: {Array}
|
||||
* Indexes of each ancestor rule within its parent rule.
|
||||
* }
|
||||
* @return {String}
|
||||
*/
|
||||
function getRuleHash(ruleData) {
|
||||
const { selector = "", ancestors = [], ruleIndex } = ruleData;
|
||||
const atRules = ancestors.reduce((acc, rule) => {
|
||||
acc += `${rule.typeName} ${(rule.conditionText || rule.name || rule.keyText)}`;
|
||||
return acc;
|
||||
}, "");
|
||||
|
||||
return `${atRules}${selector}${ruleIndex}`;
|
||||
}
|
||||
|
||||
module.exports.getSourceHash = getSourceHash;
|
||||
module.exports.getRuleHash = getRuleHash;
|
|
@ -0,0 +1,9 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'changes-utils.js',
|
||||
)
|
Загрузка…
Ссылка в новой задаче