Merge pull request #20485 from calixteman/lint_mc_css

Add a new linting task in order to detect unused/unknown css variables in the Firefox build
This commit is contained in:
calixteman 2025-12-08 09:48:18 +01:00 committed by GitHub
commit 5b22189c24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1221 additions and 22 deletions

View File

@ -32,3 +32,6 @@ jobs:
- name: Run lint-chromium
run: npx gulp lint-chromium
- name: Run lint-mozcentral
run: npx gulp lint-mozcentral

View File

@ -17,9 +17,9 @@
"float": ["inline-start", "inline-end"]
},
"length-zero-no-unit": [true, {
ignore: ["custom-properties"]
"ignore": ["custom-properties"]
}],
"selector-pseudo-element-colon-notation": "double",
"shorthand-property-no-redundant-values": true,
},
"shorthand-property-no-redundant-values": true
}
}

View File

@ -39,15 +39,22 @@ function preprocess(inFilename, outFilename, defines) {
}
function expandCssImports(content, baseUrl) {
if (defines.GECKOVIEW) {
// In Geckoview, we don't need some styles.
const startComment = "/* Ignored in GECKOVIEW: begin */";
const endComment = "/* Ignored in GECKOVIEW: end */";
const beginIndex = content.indexOf(startComment);
const endIndex = content.indexOf(endComment);
if (beginIndex >= 0 && endIndex > beginIndex) {
content =
content.substring(0, beginIndex) +
content.substring(endIndex + endComment.length);
}
}
return content.replaceAll(
/^\s*@import\s+url\(([^)]+)\);\s*$/gm,
function (all, url) {
if (defines.GECKOVIEW) {
switch (url) {
case "annotation_editor_layer_builder.css":
return "";
}
}
const file = path.join(path.dirname(baseUrl), url);
const imported = fs.readFileSync(file, "utf8").toString();
return expandCssImports(imported, file);

View File

@ -0,0 +1,110 @@
/* Copyright 2025 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TODO: Remove the exception below once someone figures out how to fix it.
// eslint-disable-next-line import/no-unresolved
import { parse, registerWalkers, Root } from "postcss-values-parser";
import { isString } from "stylelint/lib/utils/validateTypes.mjs";
import stylelint from "stylelint";
const {
createPlugin,
utils: { report, validateOptions },
} = stylelint;
registerWalkers(Root);
const ruleName = "pdfjs/no-unused-custom-properties";
// It's a very basic linter: we don't take into account scopes.
// But it should be enough for our use case.
/** @type {import('stylelint').Plugin} */
const ruleFunction =
(enabled, { ignoreList = [] } = {}, context = {}) =>
(root, result) => {
const validOptions = validateOptions(
result,
ruleName,
{
actual: enabled,
possible: [true],
},
{
actual: ignoreList,
possible: [isString],
optional: true,
}
);
if (!validOptions) {
return;
}
ignoreList = ignoreList.map(s => (s.startsWith("--") ? s : `--${s}`));
const usedCustomProperties = new Set(ignoreList);
const definedCustomProperties = new Set();
const usedBy = new Map();
root.walkDecls(decl => {
let definingProperty = null;
if (decl.prop.startsWith("--")) {
// This is a custom property definition.
definingProperty = decl.prop;
definedCustomProperties.add(definingProperty);
}
// Parse the declaration value to find var() usages.
const parsedValue = parse(decl.value);
parsedValue.walkFuncs(node => {
if (!node.isVar || node.nodes.length === 0) {
return;
}
// This is a var() function; get the custom property name.
const property = node.nodes[0].value;
if (!definingProperty) {
// This is a usage of a custom property but not in a definition.
// width: var(--foo);
usedCustomProperties.add(property);
return;
}
let usages = usedBy.get(property);
if (!usages) {
usages = [];
usedBy.set(property, usages);
}
// Record that this custom property is used by the defining property.
// --foo: var(--bar);
// bar is really used only if foo is.
usages.push(definingProperty);
});
});
const isUsed = p =>
usedCustomProperties.has(p) || (usedBy.get(p) || []).some(isUsed);
for (const customProperty of definedCustomProperties) {
if (isUsed(customProperty)) {
continue;
}
report({
message: `Custom property "${customProperty}" is defined but never used.`,
node: root,
result,
ruleName,
});
}
};
ruleFunction.ruleName = ruleName;
export default createPlugin(ruleName, ruleFunction);

View File

@ -2099,6 +2099,35 @@ gulp.task("lint", function (done) {
});
});
gulp.task(
"lint-mozcentral",
gulp.series("mozcentral", function runLintMozcentral(done) {
console.log();
console.log("### Checking mozilla-central files");
const styleLintOptions = [
"../../node_modules/stylelint/bin/stylelint.mjs",
"**/*.css",
"--report-needless-disables",
"--config",
"../../stylelint-mozcentral.json",
];
const styleLintProcess = startNode(styleLintOptions, {
stdio: "inherit",
cwd: BUILD_DIR + "mozcentral/",
});
styleLintProcess.on("close", function (styleLintCode) {
if (styleLintCode !== 0) {
done(new Error("Stylelint failed."));
return;
}
console.log("files checked, no errors found");
done();
});
})
);
gulp.task(
"lint-chromium",
gulp.series(

1032
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -45,6 +45,7 @@
"postcss-dir-pseudo-class": "^9.0.1",
"postcss-discard-comments": "^7.0.5",
"postcss-nesting": "^13.0.2",
"postcss-values-parser": "^7.0.0",
"prettier": "^3.7.2",
"puppeteer": "^24.31.0",
"stylelint": "^16.26.1",

12
stylelint-mozcentral.json Normal file
View File

@ -0,0 +1,12 @@
{
"plugins": ["./external/stylelint/no-unused-custom-properties.mjs"],
"rules": {
"no-unknown-custom-properties": true,
"pdfjs/no-unused-custom-properties": [
true,
{
"ignoreList": ["scale-round-x", "scale-round-y", "input-width"]
}
]
}
}

View File

@ -33,6 +33,23 @@
--menuitem-focus-outline-color: light-dark(#0062fa, #00cadb);
--menuitem-focus-border-color: light-dark(white, black);
--menu-bg: light-dark(white, #23222b);
--menu-background-blend-mode: normal;
--menu-box-shadow:
0 0.375px 1.5px 0 light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
0 3px 12px 0 light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.4));
--menu-border-color: light-dark(rgb(21 20 26 / 0.1), rgb(251 251 254 / 0.1));
--menuitem-border-radius: 8px;
--menu-backdrop-filter: none;
--menu-text-color: light-dark(#15141a, #fbfbfe);
--menuitem-text-hover-fg: var(--menu-text-color);
--menuitem-hover-bg: color-mix(
in srgb,
var(--menu-text-color),
transparent 86%
);
--menuitem-hover-background-blend-mode: normal;
@media screen and (forced-colors: active) {
--menu-bg: Canvas;
--menu-background-blend-mode: normal;

View File

@ -17,10 +17,11 @@
@import url(text_layer_builder.css);
@import url(annotation_layer_builder.css);
@import url(xfa_layer_builder.css);
/* Ignored in GECKOVIEW builds: */
/* Ignored in GECKOVIEW: begin */
@import url(annotation_editor_layer_builder.css);
@import url(sidebar.css);
@import url(menu.css);
/* Ignored in GECKOVIEW: end */
:root {
color-scheme: light dark;

View File

@ -26,6 +26,7 @@
--border-color-interactive: light-dark(#8f8f9d, #f9f9fa);
--border-color-interactive-hover: var(--border-color-interactive);
--border-color-interactive-active: var(--border-color-interactive);
--focus-outline-offset: 2px;
@media (forced-colors: active) {
--color-accent-primary: ButtonText;

View File

@ -16,12 +16,6 @@
@import url(pdf_viewer.css);
:root {
--dir-factor: 1;
--scale-select-width: 140px;
--toolbar-icon-opacity: 1;
--doorhanger-icon-opacity: 0.9;
--main-color: light-dark(rgb(12 12 13), rgb(249 249 250));
--body-bg-color: light-dark(rgb(212 212 215), rgb(42 42 46));
--scrollbar-color: light-dark(auto, rgb(121 121 123));
@ -46,10 +40,6 @@
--toolbarButton-download-icon: url(images/gv-toolbarButton-download.svg);
}
:root:dir(rtl) {
--dir-factor: -1;
}
@media screen and (forced-colors: active) {
:root {
--dialog-button-border: 1px solid Highlight;