mirror of
https://github.com/svg/svgo.git
synced 2025-08-01 18:46:52 +03:00
Collect stylesheet once per plugin (#1456)
computeStyle(node) in isolation is quite slow utility because it collects style elements across whole document, parses and sort them. In this diff I splitted it into `collectStylesheet(root)` and `computeStyle(stylesheet, node)` which are easy integrate with new visitor plugin api.
This commit is contained in:
68
lib/style.js
68
lib/style.js
@ -3,8 +3,7 @@
|
|||||||
const stable = require('stable');
|
const stable = require('stable');
|
||||||
const csstree = require('css-tree');
|
const csstree = require('css-tree');
|
||||||
const specificity = require('csso/lib/restructure/prepare/specificity');
|
const specificity = require('csso/lib/restructure/prepare/specificity');
|
||||||
const { selectAll, is } = require('css-select');
|
const { visit, matches } = require('./xast.js');
|
||||||
const svgoCssSelectAdapter = require('./svgo/css-select-adapter.js');
|
|
||||||
const { compareSpecificity } = require('./css-tools.js');
|
const { compareSpecificity } = require('./css-tools.js');
|
||||||
const {
|
const {
|
||||||
attrsGroups,
|
attrsGroups,
|
||||||
@ -12,11 +11,6 @@ const {
|
|||||||
presentationNonInheritableGroupAttrs,
|
presentationNonInheritableGroupAttrs,
|
||||||
} = require('../plugins/_collections.js');
|
} = require('../plugins/_collections.js');
|
||||||
|
|
||||||
const cssSelectOptions = {
|
|
||||||
xmlMode: true,
|
|
||||||
adapter: svgoCssSelectAdapter,
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseRule = (ruleNode, dynamic) => {
|
const parseRule = (ruleNode, dynamic) => {
|
||||||
let selectors;
|
let selectors;
|
||||||
let selectorsSpecificity;
|
let selectorsSpecificity;
|
||||||
@ -76,7 +70,7 @@ const parseStylesheet = (css, dynamic) => {
|
|||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
const computeOwnStyle = (node, stylesheet) => {
|
const computeOwnStyle = (stylesheet, node) => {
|
||||||
const computedStyle = {};
|
const computedStyle = {};
|
||||||
const importantStyles = new Map();
|
const importantStyles = new Map();
|
||||||
|
|
||||||
@ -90,7 +84,7 @@ const computeOwnStyle = (node, stylesheet) => {
|
|||||||
|
|
||||||
// collect matching rules
|
// collect matching rules
|
||||||
for (const { selectors, declarations, dynamic } of stylesheet) {
|
for (const { selectors, declarations, dynamic } of stylesheet) {
|
||||||
if (is(node, selectors, cssSelectOptions)) {
|
if (matches(node, selectors)) {
|
||||||
for (const { name, value, important } of declarations) {
|
for (const { name, value, important } of declarations) {
|
||||||
const computed = computedStyle[name];
|
const computed = computedStyle[name];
|
||||||
if (computed && computed.type === 'dynamic') {
|
if (computed && computed.type === 'dynamic') {
|
||||||
@ -132,43 +126,45 @@ const computeOwnStyle = (node, stylesheet) => {
|
|||||||
return computedStyle;
|
return computedStyle;
|
||||||
};
|
};
|
||||||
|
|
||||||
const computeStyle = (node) => {
|
const collectStylesheet = (root) => {
|
||||||
// find root
|
|
||||||
let root = node;
|
|
||||||
while (root.parentNode) {
|
|
||||||
root = root.parentNode;
|
|
||||||
}
|
|
||||||
// find all styles
|
|
||||||
const styleNodes = selectAll('style', root, cssSelectOptions);
|
|
||||||
// parse all styles
|
|
||||||
const stylesheet = [];
|
const stylesheet = [];
|
||||||
for (const styleNode of styleNodes) {
|
// find and parse all styles
|
||||||
const dynamic =
|
visit(root, {
|
||||||
styleNode.attributes.media != null &&
|
element: {
|
||||||
styleNode.attributes.media !== 'all';
|
enter: (node) => {
|
||||||
if (
|
if (node.name === 'style') {
|
||||||
styleNode.attributes.type == null ||
|
const dynamic =
|
||||||
styleNode.attributes.type === '' ||
|
node.attributes.media != null && node.attributes.media !== 'all';
|
||||||
styleNode.attributes.type === 'text/css'
|
if (
|
||||||
) {
|
node.attributes.type == null ||
|
||||||
const children = styleNode.children;
|
node.attributes.type === '' ||
|
||||||
for (const child of children) {
|
node.attributes.type === 'text/css'
|
||||||
if (child.type === 'text' || child.type === 'cdata') {
|
) {
|
||||||
stylesheet.push(...parseStylesheet(child.value, dynamic));
|
const children = node.children;
|
||||||
|
for (const child of children) {
|
||||||
|
if (child.type === 'text' || child.type === 'cdata') {
|
||||||
|
stylesheet.push(...parseStylesheet(child.value, dynamic));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
// sort by selectors specificity
|
// sort by selectors specificity
|
||||||
stable.inplace(stylesheet, (a, b) =>
|
stable.inplace(stylesheet, (a, b) =>
|
||||||
compareSpecificity(a.specificity, b.specificity)
|
compareSpecificity(a.specificity, b.specificity)
|
||||||
);
|
);
|
||||||
|
return stylesheet;
|
||||||
|
};
|
||||||
|
exports.collectStylesheet = collectStylesheet;
|
||||||
|
|
||||||
|
const computeStyle = (stylesheet, node) => {
|
||||||
// collect inherited styles
|
// collect inherited styles
|
||||||
const computedStyles = computeOwnStyle(node, stylesheet);
|
const computedStyles = computeOwnStyle(stylesheet, node);
|
||||||
let parent = node;
|
let parent = node;
|
||||||
while (parent.parentNode && parent.parentNode.type !== 'root') {
|
while (parent.parentNode && parent.parentNode.type !== 'root') {
|
||||||
const inheritedStyles = computeOwnStyle(parent.parentNode, stylesheet);
|
const inheritedStyles = computeOwnStyle(stylesheet, parent.parentNode);
|
||||||
for (const [name, computed] of Object.entries(inheritedStyles)) {
|
for (const [name, computed] of Object.entries(inheritedStyles)) {
|
||||||
if (
|
if (
|
||||||
computedStyles[name] == null &&
|
computedStyles[name] == null &&
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { expect } = require('chai');
|
const { expect } = require('chai');
|
||||||
const { computeStyle } = require('./style.js');
|
const { collectStylesheet, computeStyle } = require('./style.js');
|
||||||
const { querySelector } = require('./xast.js');
|
const { querySelector } = require('./xast.js');
|
||||||
const svg2js = require('./svgo/svg2js.js');
|
const svg2js = require('./svgo/svg2js.js');
|
||||||
|
|
||||||
@ -31,24 +31,35 @@ describe('computeStyle', () => {
|
|||||||
</style>
|
</style>
|
||||||
</svg>
|
</svg>
|
||||||
`);
|
`);
|
||||||
expect(computeStyle(querySelector(root, '#class'))).to.deep.equal({
|
const stylesheet = collectStylesheet(root);
|
||||||
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#class'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'red' },
|
fill: { type: 'static', inherited: false, value: 'red' },
|
||||||
});
|
});
|
||||||
expect(computeStyle(querySelector(root, '#two-classes'))).to.deep.equal({
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#two-classes'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'green' },
|
fill: { type: 'static', inherited: false, value: 'green' },
|
||||||
stroke: { type: 'static', inherited: false, value: 'black' },
|
stroke: { type: 'static', inherited: false, value: 'black' },
|
||||||
});
|
});
|
||||||
expect(computeStyle(querySelector(root, '#attribute'))).to.deep.equal({
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#attribute'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'purple' },
|
fill: { type: 'static', inherited: false, value: 'purple' },
|
||||||
});
|
});
|
||||||
expect(computeStyle(querySelector(root, '#inline-style'))).to.deep.equal({
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#inline-style'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'grey' },
|
fill: { type: 'static', inherited: false, value: 'grey' },
|
||||||
});
|
});
|
||||||
expect(computeStyle(querySelector(root, '#inheritance'))).to.deep.equal({
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#inheritance'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: true, value: 'yellow' },
|
fill: { type: 'static', inherited: true, value: 'yellow' },
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
computeStyle(querySelector(root, '#nested-inheritance'))
|
computeStyle(stylesheet, querySelector(root, '#nested-inheritance'))
|
||||||
).to.deep.equal({
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: true, value: 'blue' },
|
fill: { type: 'static', inherited: true, value: 'blue' },
|
||||||
});
|
});
|
||||||
@ -70,23 +81,33 @@ describe('computeStyle', () => {
|
|||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
`);
|
`);
|
||||||
|
const stylesheet = collectStylesheet(root);
|
||||||
expect(
|
expect(
|
||||||
computeStyle(querySelector(root, '#complex-selector'))
|
computeStyle(stylesheet, querySelector(root, '#complex-selector'))
|
||||||
).to.deep.equal({
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'red' },
|
fill: { type: 'static', inherited: false, value: 'red' },
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
computeStyle(querySelector(root, '#attribute-over-inheritance'))
|
computeStyle(
|
||||||
|
stylesheet,
|
||||||
|
querySelector(root, '#attribute-over-inheritance')
|
||||||
|
)
|
||||||
).to.deep.equal({
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'orange' },
|
fill: { type: 'static', inherited: false, value: 'orange' },
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
computeStyle(querySelector(root, '#style-rule-over-attribute'))
|
computeStyle(
|
||||||
|
stylesheet,
|
||||||
|
querySelector(root, '#style-rule-over-attribute')
|
||||||
|
)
|
||||||
).to.deep.equal({
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'blue' },
|
fill: { type: 'static', inherited: false, value: 'blue' },
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
computeStyle(querySelector(root, '#inline-style-over-style-rule'))
|
computeStyle(
|
||||||
|
stylesheet,
|
||||||
|
querySelector(root, '#inline-style-over-style-rule')
|
||||||
|
)
|
||||||
).to.deep.equal({
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'purple' },
|
fill: { type: 'static', inherited: false, value: 'purple' },
|
||||||
});
|
});
|
||||||
@ -104,18 +125,25 @@ describe('computeStyle', () => {
|
|||||||
<rect id="inline-style-over-style-rule" style="fill: purple !important;" class="b" />
|
<rect id="inline-style-over-style-rule" style="fill: purple !important;" class="b" />
|
||||||
</svg>
|
</svg>
|
||||||
`);
|
`);
|
||||||
|
const stylesheet = collectStylesheet(root);
|
||||||
expect(
|
expect(
|
||||||
computeStyle(querySelector(root, '#complex-selector'))
|
computeStyle(stylesheet, querySelector(root, '#complex-selector'))
|
||||||
).to.deep.equal({
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'green' },
|
fill: { type: 'static', inherited: false, value: 'green' },
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
computeStyle(querySelector(root, '#style-rule-over-inline-style'))
|
computeStyle(
|
||||||
|
stylesheet,
|
||||||
|
querySelector(root, '#style-rule-over-inline-style')
|
||||||
|
)
|
||||||
).to.deep.equal({
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'green' },
|
fill: { type: 'static', inherited: false, value: 'green' },
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
computeStyle(querySelector(root, '#inline-style-over-style-rule'))
|
computeStyle(
|
||||||
|
stylesheet,
|
||||||
|
querySelector(root, '#inline-style-over-style-rule')
|
||||||
|
)
|
||||||
).to.deep.equal({
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'purple' },
|
fill: { type: 'static', inherited: false, value: 'purple' },
|
||||||
});
|
});
|
||||||
@ -141,21 +169,30 @@ describe('computeStyle', () => {
|
|||||||
<rect id="static" class="c" style="fill: black" />
|
<rect id="static" class="c" style="fill: black" />
|
||||||
</svg>
|
</svg>
|
||||||
`);
|
`);
|
||||||
expect(computeStyle(querySelector(root, '#media-query'))).to.deep.equal({
|
const stylesheet = collectStylesheet(root);
|
||||||
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#media-query'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'dynamic', inherited: false },
|
fill: { type: 'dynamic', inherited: false },
|
||||||
});
|
});
|
||||||
expect(computeStyle(querySelector(root, '#hover'))).to.deep.equal({
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#hover'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'dynamic', inherited: false },
|
fill: { type: 'dynamic', inherited: false },
|
||||||
});
|
});
|
||||||
expect(computeStyle(querySelector(root, '#inherited'))).to.deep.equal({
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#inherited'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'dynamic', inherited: true },
|
fill: { type: 'dynamic', inherited: true },
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
computeStyle(querySelector(root, '#inherited-overriden'))
|
computeStyle(stylesheet, querySelector(root, '#inherited-overriden'))
|
||||||
).to.deep.equal({
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'blue' },
|
fill: { type: 'static', inherited: false, value: 'blue' },
|
||||||
});
|
});
|
||||||
expect(computeStyle(querySelector(root, '#static'))).to.deep.equal({
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#static'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'black' },
|
fill: { type: 'static', inherited: false, value: 'black' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -177,13 +214,20 @@ describe('computeStyle', () => {
|
|||||||
<rect id="static" class="c" />
|
<rect id="static" class="c" />
|
||||||
</svg>
|
</svg>
|
||||||
`);
|
`);
|
||||||
expect(computeStyle(querySelector(root, '#media-query'))).to.deep.equal({
|
const stylesheet = collectStylesheet(root);
|
||||||
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#media-query'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'dynamic', inherited: false },
|
fill: { type: 'dynamic', inherited: false },
|
||||||
});
|
});
|
||||||
expect(computeStyle(querySelector(root, '#kinda-static'))).to.deep.equal({
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#kinda-static'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'dynamic', inherited: false },
|
fill: { type: 'dynamic', inherited: false },
|
||||||
});
|
});
|
||||||
expect(computeStyle(querySelector(root, '#static'))).to.deep.equal({
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#static'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'blue' },
|
fill: { type: 'static', inherited: false, value: 'blue' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -205,15 +249,20 @@ describe('computeStyle', () => {
|
|||||||
<rect id="invalid-type" class="c" />
|
<rect id="invalid-type" class="c" />
|
||||||
</svg>
|
</svg>
|
||||||
`);
|
`);
|
||||||
expect(computeStyle(querySelector(root, '#valid-type'))).to.deep.equal({
|
const stylesheet = collectStylesheet(root);
|
||||||
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#valid-type'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'red' },
|
fill: { type: 'static', inherited: false, value: 'red' },
|
||||||
});
|
});
|
||||||
expect(computeStyle(querySelector(root, '#empty-type'))).to.deep.equal({
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#empty-type'))
|
||||||
|
).to.deep.equal({
|
||||||
fill: { type: 'static', inherited: false, value: 'green' },
|
fill: { type: 'static', inherited: false, value: 'green' },
|
||||||
});
|
});
|
||||||
expect(computeStyle(querySelector(root, '#invalid-type'))).to.deep.equal(
|
expect(
|
||||||
{}
|
computeStyle(stylesheet, querySelector(root, '#invalid-type'))
|
||||||
);
|
).to.deep.equal({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores keyframes atrule', () => {
|
it('ignores keyframes atrule', () => {
|
||||||
@ -238,7 +287,10 @@ describe('computeStyle', () => {
|
|||||||
<rect id="element" class="a" />
|
<rect id="element" class="a" />
|
||||||
</svg>
|
</svg>
|
||||||
`);
|
`);
|
||||||
expect(computeStyle(querySelector(root, '#element'))).to.deep.equal({
|
const stylesheet = collectStylesheet(root);
|
||||||
|
expect(
|
||||||
|
computeStyle(stylesheet, querySelector(root, '#element'))
|
||||||
|
).to.deep.equal({
|
||||||
animation: {
|
animation: {
|
||||||
type: 'static',
|
type: 'static',
|
||||||
inherited: false,
|
inherited: false,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { computeStyle } = require('../lib/style.js');
|
const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
||||||
const { pathElems } = require('./_collections.js');
|
const { pathElems } = require('./_collections.js');
|
||||||
const { path2js, js2path } = require('./_path.js');
|
const { path2js, js2path } = require('./_path.js');
|
||||||
const { applyTransforms } = require('./_applyTransforms.js');
|
const { applyTransforms } = require('./_applyTransforms.js');
|
||||||
@ -55,11 +55,12 @@ let arcTolerance;
|
|||||||
* @author Kir Belevich
|
* @author Kir Belevich
|
||||||
*/
|
*/
|
||||||
exports.fn = (root, params) => {
|
exports.fn = (root, params) => {
|
||||||
|
const stylesheet = collectStylesheet(root);
|
||||||
return {
|
return {
|
||||||
element: {
|
element: {
|
||||||
enter: (node) => {
|
enter: (node) => {
|
||||||
if (pathElems.includes(node.name) && node.attributes.d != null) {
|
if (pathElems.includes(node.name) && node.attributes.d != null) {
|
||||||
const computedStyle = computeStyle(node);
|
const computedStyle = computeStyle(stylesheet, node);
|
||||||
precision = params.floatPrecision;
|
precision = params.floatPrecision;
|
||||||
error =
|
error =
|
||||||
precision !== false
|
precision !== false
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { detachNodeFromParent } = require('../lib/xast.js');
|
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||||
const { computeStyle } = require('../lib/style.js');
|
const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
||||||
const { path2js, js2path, intersects } = require('./_path.js');
|
const { path2js, js2path, intersects } = require('./_path.js');
|
||||||
|
|
||||||
exports.type = 'visitor';
|
exports.type = 'visitor';
|
||||||
@ -22,6 +22,7 @@ exports.fn = (root, params) => {
|
|||||||
floatPrecision,
|
floatPrecision,
|
||||||
noSpaceAfterFlags = false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
|
noSpaceAfterFlags = false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
|
||||||
} = params;
|
} = params;
|
||||||
|
const stylesheet = collectStylesheet(root);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
element: {
|
element: {
|
||||||
@ -53,7 +54,7 @@ exports.fn = (root, params) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// preserve paths with markers
|
// preserve paths with markers
|
||||||
const computedStyle = computeStyle(child);
|
const computedStyle = computeStyle(stylesheet, child);
|
||||||
if (
|
if (
|
||||||
computedStyle['marker-start'] ||
|
computedStyle['marker-start'] ||
|
||||||
computedStyle['marker-mid'] ||
|
computedStyle['marker-mid'] ||
|
||||||
|
@ -5,7 +5,7 @@ const {
|
|||||||
closestByName,
|
closestByName,
|
||||||
detachNodeFromParent,
|
detachNodeFromParent,
|
||||||
} = require('../lib/xast.js');
|
} = require('../lib/xast.js');
|
||||||
const { computeStyle } = require('../lib/style.js');
|
const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
||||||
const { parsePathData } = require('../lib/path.js');
|
const { parsePathData } = require('../lib/path.js');
|
||||||
|
|
||||||
exports.type = 'visitor';
|
exports.type = 'visitor';
|
||||||
@ -49,12 +49,14 @@ exports.fn = (root, params) => {
|
|||||||
polylineEmptyPoints = true,
|
polylineEmptyPoints = true,
|
||||||
polygonEmptyPoints = true,
|
polygonEmptyPoints = true,
|
||||||
} = params;
|
} = params;
|
||||||
|
const stylesheet = collectStylesheet(root);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
element: {
|
element: {
|
||||||
enter: (node) => {
|
enter: (node) => {
|
||||||
// Removes hidden elements
|
// Removes hidden elements
|
||||||
// https://www.w3schools.com/cssref/pr_class_visibility.asp
|
// https://www.w3schools.com/cssref/pr_class_visibility.asp
|
||||||
const computedStyle = computeStyle(node);
|
const computedStyle = computeStyle(stylesheet, node);
|
||||||
if (
|
if (
|
||||||
isHidden &&
|
isHidden &&
|
||||||
computedStyle.visibility &&
|
computedStyle.visibility &&
|
||||||
|
Reference in New Issue
Block a user