summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/compatibility/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/server/actors/compatibility/lib
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/server/actors/compatibility/lib')
-rw-r--r--devtools/server/actors/compatibility/lib/MDNCompatibility.js327
-rw-r--r--devtools/server/actors/compatibility/lib/moz.build11
-rw-r--r--devtools/server/actors/compatibility/lib/test/xpcshell/.eslintrc.js6
-rw-r--r--devtools/server/actors/compatibility/lib/test/xpcshell/head.js10
-rw-r--r--devtools/server/actors/compatibility/lib/test/xpcshell/test_mdn-compatibility.js193
-rw-r--r--devtools/server/actors/compatibility/lib/test/xpcshell/xpcshell.ini7
6 files changed, 554 insertions, 0 deletions
diff --git a/devtools/server/actors/compatibility/lib/MDNCompatibility.js b/devtools/server/actors/compatibility/lib/MDNCompatibility.js
new file mode 100644
index 0000000000..9975123103
--- /dev/null
+++ b/devtools/server/actors/compatibility/lib/MDNCompatibility.js
@@ -0,0 +1,327 @@
+/* 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";
+
+const _SUPPORT_STATE_BROWSER_NOT_FOUND = "BROWSER_NOT_FOUND";
+const _SUPPORT_STATE_SUPPORTED = "SUPPORTED";
+const _SUPPORT_STATE_UNSUPPORTED = "UNSUPPORTED";
+const _SUPPORT_STATE_UNSUPPORTED_PREFIX_NEEDED = "UNSUPPORTED_PREFIX_NEEDED";
+
+loader.lazyRequireGetter(
+ this,
+ "COMPATIBILITY_ISSUE_TYPE",
+ "resource://devtools/shared/constants.js",
+ true
+);
+
+loader.lazyRequireGetter(
+ this,
+ ["getCompatNode", "getCompatTable"],
+ "resource://devtools/shared/compatibility/helpers.js",
+ true
+);
+
+const PREFIX_REGEX = /^-\w+-/;
+
+/**
+ * A class with methods used to query the MDN compatibility data for CSS properties and
+ * HTML nodes and attributes for specific browsers and versions.
+ */
+class MDNCompatibility {
+ /**
+ * Constructor.
+ *
+ * @param {JSON} cssPropertiesCompatData
+ * JSON of the compat data for CSS properties.
+ * https://github.com/mdn/browser-compat-data/tree/master/css/properties
+ */
+ constructor(cssPropertiesCompatData) {
+ this._cssPropertiesCompatData = cssPropertiesCompatData;
+ }
+
+ /**
+ * Return the CSS related compatibility issues from given CSS declaration blocks.
+ *
+ * @param {Array} declarations
+ * CSS declarations to check.
+ * e.g. [{ name: "background-color", value: "lime" }, ...]
+ * @param {Array} browsers
+ * Restrict compatibility checks to these browsers and versions.
+ * e.g. [{ id: "firefox", name: "Firefox", version: "68" }, ...]
+ * @return {Array} issues
+ */
+ getCSSDeclarationBlockIssues(declarations, browsers) {
+ const summaries = [];
+ for (const { name: property } of declarations) {
+ // Ignore CSS custom properties as any name is valid.
+ if (property.startsWith("--")) {
+ continue;
+ }
+
+ summaries.push(this._getCSSPropertyCompatSummary(browsers, property));
+ }
+
+ // Classify to aliases summaries and normal summaries.
+ const { aliasSummaries, normalSummaries } =
+ this._classifyCSSCompatSummaries(summaries, browsers);
+
+ // Finally, convert to CSS issues.
+ return this._toCSSIssues(normalSummaries.concat(aliasSummaries));
+ }
+
+ /**
+ * Classify the compatibility summaries that are able to get from
+ * `getCSSPropertyCompatSummary`.
+ * There are CSS properties that can specify the style with plural aliases such as
+ * `user-select`, aggregates those as the aliases summaries.
+ *
+ * @param {Array} summaries
+ * Assume the result of _getCSSPropertyCompatSummary().
+ * @param {Array} browsers
+ * All browsers that to check
+ * e.g. [{ id: "firefox", name: "Firefox", version: "68" }, ...]
+ * @return Object
+ * {
+ * aliasSummaries: Array of alias summary,
+ * normalSummaries: Array of normal summary
+ * }
+ */
+ _classifyCSSCompatSummaries(summaries, browsers) {
+ const aliasSummariesMap = new Map();
+ const normalSummaries = summaries.filter(s => {
+ const {
+ database,
+ invalid,
+ terms,
+ unsupportedBrowsers,
+ prefixNeededBrowsers,
+ } = s;
+
+ if (invalid) {
+ return true;
+ }
+
+ const alias = this._getAlias(database, terms);
+ if (!alias) {
+ return true;
+ }
+
+ if (!aliasSummariesMap.has(alias)) {
+ aliasSummariesMap.set(
+ alias,
+ Object.assign(s, {
+ property: alias,
+ aliases: [],
+ unsupportedBrowsers: browsers,
+ prefixNeededBrowsers: browsers,
+ })
+ );
+ }
+
+ // Update alias summary.
+ const terminal = terms.pop();
+ const aliasSummary = aliasSummariesMap.get(alias);
+ if (!aliasSummary.aliases.includes(terminal)) {
+ aliasSummary.aliases.push(terminal);
+ }
+ aliasSummary.unsupportedBrowsers =
+ aliasSummary.unsupportedBrowsers.filter(b =>
+ unsupportedBrowsers.includes(b)
+ );
+ aliasSummary.prefixNeededBrowsers =
+ aliasSummary.prefixNeededBrowsers.filter(b =>
+ prefixNeededBrowsers.includes(b)
+ );
+ return false;
+ });
+
+ const aliasSummaries = [...aliasSummariesMap.values()].map(s => {
+ s.prefixNeeded = s.prefixNeededBrowsers.length !== 0;
+ return s;
+ });
+
+ return { aliasSummaries, normalSummaries };
+ }
+
+ _getAlias(compatNode, terms) {
+ const targetNode = getCompatNode(compatNode, terms);
+ return targetNode ? targetNode._aliasOf : null;
+ }
+
+ /**
+ * Return the compatibility summary of the terms.
+ *
+ * @param {Array} browsers
+ * All browsers that to check
+ * e.g. [{ id: "firefox", name: "Firefox", version: "68" }, ...]
+ * @param {Array} database
+ * MDN compatibility dataset where finds from
+ * @param {Array} terms
+ * The terms which is checked the compatibility summary from the
+ * database. The paremeters are passed as `rest parameters`.
+ * e.g. _getCompatSummary(browsers, database, "user-select", ...)
+ * @return {Object}
+ * {
+ * database: The passed database as a parameter,
+ * terms: The passed terms as a parameter,
+ * url: The link which indicates the spec in MDN,
+ * deprecated: true if the spec of terms is deprecated,
+ * experimental: true if the spec of terms is experimental,
+ * unsupportedBrowsers: Array of unsupported browsers,
+ * }
+ */
+ _getCompatSummary(browsers, database, terms) {
+ const compatTable = getCompatTable(database, terms);
+
+ if (!compatTable) {
+ return { invalid: true, unsupportedBrowsers: [] };
+ }
+
+ const unsupportedBrowsers = [];
+ const prefixNeededBrowsers = [];
+
+ for (const browser of browsers) {
+ const state = this._getSupportState(
+ compatTable,
+ browser,
+ database,
+ terms
+ );
+
+ switch (state) {
+ case _SUPPORT_STATE_UNSUPPORTED_PREFIX_NEEDED: {
+ prefixNeededBrowsers.push(browser);
+ unsupportedBrowsers.push(browser);
+ break;
+ }
+ case _SUPPORT_STATE_UNSUPPORTED: {
+ unsupportedBrowsers.push(browser);
+ break;
+ }
+ }
+ }
+
+ const { deprecated, experimental } = compatTable.status || {};
+
+ return {
+ database,
+ terms,
+ url: compatTable.mdn_url,
+ specUrl: compatTable.spec_url,
+ deprecated,
+ experimental,
+ unsupportedBrowsers,
+ prefixNeededBrowsers,
+ };
+ }
+
+ /**
+ * Return the compatibility summary of the CSS property.
+ * This function just adds `property` filed to the result of `_getCompatSummary`.
+ *
+ * @param {Array} browsers
+ * All browsers that to check
+ * e.g. [{ id: "firefox", name: "Firefox", version: "68" }, ...]
+ * @return {Object} compatibility summary
+ */
+ _getCSSPropertyCompatSummary(browsers, property) {
+ const summary = this._getCompatSummary(
+ browsers,
+ this._cssPropertiesCompatData,
+ [property]
+ );
+ return Object.assign(summary, { property });
+ }
+
+ _getSupportState(compatTable, browser, compatNode, terms) {
+ const supportList = compatTable.support[browser.id];
+ if (!supportList) {
+ return _SUPPORT_STATE_BROWSER_NOT_FOUND;
+ }
+
+ const version = parseFloat(browser.version);
+ const terminal = terms.at(-1);
+ const prefix = terminal.match(PREFIX_REGEX)?.[0];
+
+ let prefixNeeded = false;
+ for (const support of supportList) {
+ const { alternative_name: alternativeName, added, removed } = support;
+
+ if (
+ // added id true when feature is supported, but we don't know the version
+ (added === true ||
+ // `null` and `undefined` is when we don't know if it's supported.
+ // Since we don't want to have false negative, we consider it as supported
+ added === null ||
+ added === undefined ||
+ // It was added on a previous version number
+ added <= version) &&
+ // `added` is false when the property isn't supported
+ added !== false &&
+ // `removed` is false when the feature wasn't removevd
+ (removed === false ||
+ // `null` and `undefined` is when we don't know if it was removed.
+ // Since we don't want to have false negative, we consider it as supported
+ removed === null ||
+ removed === undefined ||
+ // It was removed, but on a later version, so it's still supported
+ version <= removed)
+ ) {
+ if (alternativeName) {
+ if (alternativeName === terminal) {
+ return _SUPPORT_STATE_SUPPORTED;
+ }
+ } else if (
+ support.prefix === prefix ||
+ // There are compat data that are defined with prefix like "-moz-binding".
+ // In this case, we don't have to check the prefix.
+ (prefix && !this._getAlias(compatNode, terms))
+ ) {
+ return _SUPPORT_STATE_SUPPORTED;
+ }
+
+ prefixNeeded = true;
+ }
+ }
+
+ return prefixNeeded
+ ? _SUPPORT_STATE_UNSUPPORTED_PREFIX_NEEDED
+ : _SUPPORT_STATE_UNSUPPORTED;
+ }
+
+ _hasIssue({ unsupportedBrowsers, deprecated, experimental, invalid }) {
+ // Don't apply as issue the invalid term which was not in the database.
+ return (
+ !invalid && (unsupportedBrowsers.length || deprecated || experimental)
+ );
+ }
+
+ _toIssue(summary, type) {
+ const issue = Object.assign({}, summary, { type });
+ delete issue.database;
+ delete issue.terms;
+ delete issue.prefixNeededBrowsers;
+ return issue;
+ }
+
+ _toCSSIssues(summaries) {
+ const issues = [];
+
+ for (const summary of summaries) {
+ if (!this._hasIssue(summary)) {
+ continue;
+ }
+
+ const type = summary.aliases
+ ? COMPATIBILITY_ISSUE_TYPE.CSS_PROPERTY_ALIASES
+ : COMPATIBILITY_ISSUE_TYPE.CSS_PROPERTY;
+ issues.push(this._toIssue(summary, type));
+ }
+
+ return issues;
+ }
+}
+
+module.exports = MDNCompatibility;
diff --git a/devtools/server/actors/compatibility/lib/moz.build b/devtools/server/actors/compatibility/lib/moz.build
new file mode 100644
index 0000000000..f28d8fe482
--- /dev/null
+++ b/devtools/server/actors/compatibility/lib/moz.build
@@ -0,0 +1,11 @@
+# -*- 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/.
+
+XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell/xpcshell.ini"]
+
+DevToolsModules(
+ "MDNCompatibility.js",
+)
diff --git a/devtools/server/actors/compatibility/lib/test/xpcshell/.eslintrc.js b/devtools/server/actors/compatibility/lib/test/xpcshell/.eslintrc.js
new file mode 100644
index 0000000000..65efbdee13
--- /dev/null
+++ b/devtools/server/actors/compatibility/lib/test/xpcshell/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ extends: "../../../../../../.eslintrc.xpcshell.js",
+};
diff --git a/devtools/server/actors/compatibility/lib/test/xpcshell/head.js b/devtools/server/actors/compatibility/lib/test/xpcshell/head.js
new file mode 100644
index 0000000000..733c0400da
--- /dev/null
+++ b/devtools/server/actors/compatibility/lib/test/xpcshell/head.js
@@ -0,0 +1,10 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
+const { require } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+);
diff --git a/devtools/server/actors/compatibility/lib/test/xpcshell/test_mdn-compatibility.js b/devtools/server/actors/compatibility/lib/test/xpcshell/test_mdn-compatibility.js
new file mode 100644
index 0000000000..e411feb3b0
--- /dev/null
+++ b/devtools/server/actors/compatibility/lib/test/xpcshell/test_mdn-compatibility.js
@@ -0,0 +1,193 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test for the MDN compatibility diagnosis module.
+
+const {
+ COMPATIBILITY_ISSUE_TYPE,
+} = require("resource://devtools/shared/constants.js");
+const MDNCompatibility = require("resource://devtools/server/actors/compatibility/lib/MDNCompatibility.js");
+const cssPropertiesCompatData = require("resource://devtools/shared/compatibility/dataset/css-properties.json");
+
+const mdnCompatibility = new MDNCompatibility(cssPropertiesCompatData);
+
+const FIREFOX_1 = {
+ id: "firefox",
+ version: "1",
+};
+
+const FIREFOX_60 = {
+ id: "firefox",
+ version: "60",
+};
+
+const FIREFOX_69 = {
+ id: "firefox",
+ version: "69",
+};
+
+const FIREFOX_ANDROID_1 = {
+ id: "firefox_android",
+ version: "1",
+};
+
+const SAFARI_13 = {
+ id: "safari",
+ version: "13",
+};
+
+const TEST_DATA = [
+ {
+ description: "Test for a supported property",
+ declarations: [{ name: "background-color" }],
+ browsers: [FIREFOX_69],
+ expectedIssues: [],
+ },
+ {
+ description: "Test for some supported properties",
+ declarations: [{ name: "background-color" }, { name: "color" }],
+ browsers: [FIREFOX_69],
+ expectedIssues: [],
+ },
+ {
+ description: "Test for an unsupported property",
+ declarations: [{ name: "grid-column" }],
+ browsers: [FIREFOX_1],
+ expectedIssues: [
+ {
+ type: COMPATIBILITY_ISSUE_TYPE.CSS_PROPERTY,
+ property: "grid-column",
+ url: "https://developer.mozilla.org/docs/Web/CSS/grid-column",
+ specUrl: "https://drafts.csswg.org/css-grid/#placement-shorthands",
+ deprecated: false,
+ experimental: false,
+ unsupportedBrowsers: [FIREFOX_1],
+ },
+ ],
+ },
+ {
+ description: "Test for an unknown property",
+ declarations: [{ name: "unknown-property" }],
+ browsers: [FIREFOX_69],
+ expectedIssues: [],
+ },
+ {
+ description: "Test for a deprecated property",
+ declarations: [{ name: "clip" }],
+ browsers: [FIREFOX_69],
+ expectedIssues: [
+ {
+ type: COMPATIBILITY_ISSUE_TYPE.CSS_PROPERTY,
+ property: "clip",
+ url: "https://developer.mozilla.org/docs/Web/CSS/clip",
+ specUrl: "https://drafts.fxtf.org/css-masking/#clip-property",
+ deprecated: true,
+ experimental: false,
+ unsupportedBrowsers: [],
+ },
+ ],
+ },
+ {
+ description: "Test for a property having some issues",
+ declarations: [{ name: "ruby-align" }],
+ browsers: [FIREFOX_1],
+ expectedIssues: [
+ {
+ type: COMPATIBILITY_ISSUE_TYPE.CSS_PROPERTY,
+ property: "ruby-align",
+ url: "https://developer.mozilla.org/docs/Web/CSS/ruby-align",
+ specUrl: "https://drafts.csswg.org/css-ruby/#ruby-align-property",
+ deprecated: false,
+ experimental: true,
+ unsupportedBrowsers: [FIREFOX_1],
+ },
+ ],
+ },
+ {
+ description:
+ "Test for an aliased property not supported in all browsers with prefix needed",
+ declarations: [{ name: "-moz-user-select" }],
+ browsers: [FIREFOX_69, SAFARI_13],
+ expectedIssues: [
+ {
+ type: COMPATIBILITY_ISSUE_TYPE.CSS_PROPERTY_ALIASES,
+ property: "user-select",
+ aliases: ["-moz-user-select"],
+ url: "https://developer.mozilla.org/docs/Web/CSS/user-select",
+ specUrl: "https://drafts.csswg.org/css-ui/#content-selection",
+ deprecated: false,
+ experimental: false,
+ prefixNeeded: true,
+ unsupportedBrowsers: [SAFARI_13],
+ },
+ ],
+ },
+ {
+ description:
+ "Test for an aliased property not supported in all browsers without prefix needed",
+ declarations: [
+ { name: "-moz-user-select" },
+ { name: "-webkit-user-select" },
+ ],
+ browsers: [FIREFOX_ANDROID_1, FIREFOX_69, SAFARI_13],
+ expectedIssues: [
+ {
+ type: COMPATIBILITY_ISSUE_TYPE.CSS_PROPERTY_ALIASES,
+ property: "user-select",
+ aliases: ["-moz-user-select", "-webkit-user-select"],
+ url: "https://developer.mozilla.org/docs/Web/CSS/user-select",
+ specUrl: "https://drafts.csswg.org/css-ui/#content-selection",
+ deprecated: false,
+ experimental: false,
+ prefixNeeded: false,
+ unsupportedBrowsers: [FIREFOX_ANDROID_1],
+ },
+ ],
+ },
+ {
+ description: "Test for aliased properties supported in all browsers",
+ declarations: [
+ { name: "-moz-user-select" },
+ { name: "-webkit-user-select" },
+ ],
+ browsers: [FIREFOX_69, SAFARI_13],
+ expectedIssues: [],
+ },
+ {
+ description: "Test for a property defined with prefix",
+ declarations: [{ name: "-moz-user-input" }],
+ browsers: [FIREFOX_1, FIREFOX_60, FIREFOX_69],
+ expectedIssues: [
+ {
+ type: COMPATIBILITY_ISSUE_TYPE.CSS_PROPERTY,
+ property: "-moz-user-input",
+ url: "https://developer.mozilla.org/docs/Web/CSS/-moz-user-input",
+ specUrl: undefined,
+ deprecated: true,
+ experimental: false,
+ unsupportedBrowsers: [],
+ },
+ ],
+ },
+];
+
+add_task(() => {
+ for (const {
+ description,
+ declarations,
+ browsers,
+ expectedIssues,
+ } of TEST_DATA) {
+ info(description);
+ const issues = mdnCompatibility.getCSSDeclarationBlockIssues(
+ declarations,
+ browsers
+ );
+ deepEqual(
+ issues,
+ expectedIssues,
+ "CSS declaration compatibility data matches expectations"
+ );
+ }
+});
diff --git a/devtools/server/actors/compatibility/lib/test/xpcshell/xpcshell.ini b/devtools/server/actors/compatibility/lib/test/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..490afa9504
--- /dev/null
+++ b/devtools/server/actors/compatibility/lib/test/xpcshell/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = devtools
+head = head.js
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_mdn-compatibility.js]