fix: Explicitly handle null attribute values (#1157)

* fix: Explicitly handle removed attributes

The attribute `value` can be null when a mutation observer triggers due to a removed attribute. This is currently not reflected by types and code.

* Apply formatting changes

* fix

* add changeset
This commit is contained in:
Francesco Novy
2023-02-27 20:32:56 +01:00
committed by GitHub
parent e65465e808
commit 8e47ca1021
7 changed files with 256 additions and 15 deletions

View File

@@ -149,7 +149,7 @@ function buildNode(
* They often overwrite other attributes on the element.
* We need to parse them last so they can overwrite conflicting attributes.
*/
const specialAttributes: attributes = {};
const specialAttributes: { [key: string]: string | number } = {};
for (const name in n.attributes) {
if (!Object.prototype.hasOwnProperty.call(n.attributes, name)) {
continue;
@@ -165,6 +165,11 @@ function buildNode(
continue;
}
// null values mean the attribute was removed
if (value === null) {
continue;
}
/**
* Boolean attributes are considered to be true if they're present on the element at all.
* We should set value to the empty string ("") or the attribute's name, with no leading or trailing whitespace.

View File

@@ -223,33 +223,36 @@ export function transformAttribute(
doc: Document,
tagName: string,
name: string,
value: string,
): string {
value: string | null,
): string | null {
if (!value) {
return value;
}
// relative path in attribute
if (
name === 'src' ||
(name === 'href' && value && !(tagName === 'use' && value[0] === '#'))
(name === 'href' && !(tagName === 'use' && value[0] === '#'))
) {
// href starts with a # is an id pointer for svg
return absoluteToDoc(doc, value);
} else if (name === 'xlink:href' && value && value[0] !== '#') {
} else if (name === 'xlink:href' && value[0] !== '#') {
// xlink:href starts with # is an id pointer
return absoluteToDoc(doc, value);
} else if (
name === 'background' &&
value &&
(tagName === 'table' || tagName === 'td' || tagName === 'th')
) {
return absoluteToDoc(doc, value);
} else if (name === 'srcset' && value) {
} else if (name === 'srcset') {
return getAbsoluteSrcsetString(doc, value);
} else if (name === 'style' && value) {
} else if (name === 'style') {
return absoluteToStylesheet(value, getHref());
} else if (tagName === 'object' && name === 'data' && value) {
} else if (tagName === 'object' && name === 'data') {
return absoluteToDoc(doc, value);
} else {
return value;
}
return value;
}
export function _isBlockedElement(
@@ -794,8 +797,10 @@ function serializeElementNode(
};
}
function lowerIfExists(maybeAttr: string | number | boolean): string {
if (maybeAttr === undefined) {
function lowerIfExists(
maybeAttr: string | number | boolean | undefined | null,
): string {
if (maybeAttr === undefined || maybeAttr === null) {
return '';
} else {
return (maybeAttr as string).toLowerCase();

View File

@@ -21,7 +21,7 @@ export type documentTypeNode = {
};
export type attributes = {
[key: string]: string | number | true;
[key: string]: string | number | true | null;
};
export type legacyAttributes = {
/**