in src/schema/validator.js [492:731]
_addCustomKeywords(validator) {
validator.removeKeyword(SCHEMA_KEYWORDS.DEPRECATED);
validator.addKeyword({
keyword: SCHEMA_KEYWORDS.DEPRECATED,
validate: function validateDeprecated(
message,
propValue,
schema,
{ instancePath }
) {
if (
!Object.prototype.hasOwnProperty.call(
DEPRECATED_MANIFEST_PROPERTIES,
instancePath
)
) {
// Do not emit errors for every deprecated property, as it may introduce
// regressions due to unexpected new deprecation messages raised as errors,
// better to deal with it separately.
return true;
}
validateDeprecated.errors = [
{
keyword: SCHEMA_KEYWORDS.DEPRECATED,
message,
},
];
return false;
},
errors: true,
});
function createManifestVersionValidateFn(keyword, condFn) {
// function of type SchemaValidateFunction (see ajv typescript signatures).
return function validate(
keywordSchemaValue,
propValue,
schema,
{ rootData /* instancePath, parentData, parentDataProperty, */ }
) {
const manifestVersion =
(rootData && rootData.manifest_version) || MANIFEST_VERSION_DEFAULT;
const res = condFn(keywordSchemaValue, manifestVersion);
// If the min/max_manifest_version is set on a schema entry of type array,
// propagate the same keyword to the `items` schema, which is needed to
// - be able to recognize that those schema entries are also only allowed on
// certain manifest versions (which becomes part of the linting messages)
// - be able to filter out the validation errors related to future (not yet
// supported) manifest versions if they are related to those schema entries
// (which happens based on the current or parent schema in the `filterErrors`
// helper method).
if (schema.type === 'array') {
// TODO(#3774): move this at "import JSONSchema data" time, and remove it from here.
// eslint-disable-next-line no-param-reassign
schema.items[keyword] = schema[keyword];
}
if (!res) {
// If the addon manifest is out of an enum values min/max manifest version range,
// don't report an additional validation error for the min/max_manifest_version
// keyword validation function.
if (schema.enum) {
return true;
}
validate.errors = [
{
keyword,
params: { [keyword]: keywordSchemaValue },
},
];
}
return res;
};
}
validator.addKeyword({
keyword: SCHEMA_KEYWORDS.MAX_MANIFEST_VERSION,
// function of type SchemaValidateFunction (see ajv typescript signatures).
validate: createManifestVersionValidateFn(
SCHEMA_KEYWORDS.MAX_MANIFEST_VERSION,
(maxMV, manifestVersion) => maxMV >= manifestVersion
),
errors: true,
});
validator.addKeyword({
keyword: SCHEMA_KEYWORDS.MIN_MANIFEST_VERSION,
validate: createManifestVersionValidateFn(
SCHEMA_KEYWORDS.MIN_MANIFEST_VERSION,
(minMV, manifestVersion) => minMV <= manifestVersion
),
errors: true,
});
const validatePrivilegedPermissions = (keywordSchemaValue, propValue) => {
const privilegedPermissions = this.getPrivilegedPermissionsSet(validator);
const found = new Set();
for (const permission of propValue) {
if (privilegedPermissions.has(permission)) {
found.add(permission);
}
}
const hasMozillaAddonsPermission = found.has('mozillaAddons');
// If the addon is expected to be privileged, we report a linting error if:
// - there are no privileged permissions required
// - and/or if the "mozillaAddons" permission isn't requested (which is going
// to be a mandatory requirement even if there are also other privileged
// permissions already required).
if (this.isPrivilegedAddon) {
if (found.size === 0 || !hasMozillaAddonsPermission) {
validatePrivilegedPermissions.errors = [
{
keyword: SCHEMA_KEYWORDS.VALIDATE_PRIVILEGED_PERMISSIONS,
params: {
postprocess: keywordSchemaValue,
privilegedPermissions: Array.from(found),
hasMozillaAddonsPermission,
},
},
];
return false;
}
return true;
}
if (found.size > 0) {
validatePrivilegedPermissions.errors = [
{
keyword: SCHEMA_KEYWORDS.VALIDATE_PRIVILEGED_PERMISSIONS,
params: {
postprocess: keywordSchemaValue,
privilegedPermissions: Array.from(found),
hasMozillaAddonsPermission: found.has('mozillaAddons'),
},
},
];
return false;
}
return true;
};
validator.addKeyword({
keyword: SCHEMA_KEYWORDS.VALIDATE_PRIVILEGED_PERMISSIONS,
validate: validatePrivilegedPermissions,
});
const validatePrivilegedManifestFields = (
keywordSchemaValue,
propValue,
schema,
{ rootData /* instancePath, parentData, parentDataProperty, */ }
) => {
const hasMozillaAddonsPermission =
Array.isArray(rootData.permissions) &&
rootData.permissions.includes('mozillaAddons');
if (this.isPrivilegedAddon) {
if (!hasMozillaAddonsPermission) {
validatePrivilegedManifestFields.errors = [
{
keyword: SCHEMA_KEYWORDS.PRIVILEGED,
params: {
hasMozillaAddonsPermission,
},
},
];
return false;
}
return true;
}
validatePrivilegedManifestFields.errors = [
{
keyword: SCHEMA_KEYWORDS.PRIVILEGED,
params: {
hasMozillaAddonsPermission,
},
},
];
return false;
};
validator.addKeyword({
keyword: SCHEMA_KEYWORDS.PRIVILEGED,
validate: validatePrivilegedManifestFields,
});
const validateRequiredManifestBackgroundKeys = (
keywordSchemaValue,
propValue,
schema,
{ rootData /* instancePath, parentData, parentDataProperty, */ }
) => {
if (keywordSchemaValue !== 'checkRequiredManifestBackgroundKeys') {
return true;
}
// At least one environment is required
if (
!propValue.page &&
!propValue.scripts?.length &&
!propValue.service_worker
) {
// Add an error to the manifest validations.
const serviceWorkerEnabled =
this.enableBackgroundServiceWorker &&
rootData.manifest_version === 3 &&
this.allowedManifestVersionsRange.maximum >= 3;
const message = `requires at least one of ${
serviceWorkerEnabled ? '"service_worker", ' : ''
}"scripts" or "page".`;
// Report the error as coming from a `required` keyword enforcing
// which properties are required to be part of the `background`.
validateRequiredManifestBackgroundKeys.errors = [
{
keyword: SCHEMA_KEYWORDS.REQUIRED,
message,
params: {
preprocess: keywordSchemaValue,
},
},
];
return false;
}
return true;
};
validator.addKeyword({
keyword: 'postprocess',
validate: validateRequiredManifestBackgroundKeys,
});
}