in pkg/pub_validations/lib/html/html_validation.dart [30:124]
void validateHtml(Node root) {
List<Element> elements;
List<Element> links;
List<Element> scripts;
List<Element> buttons;
if (root is DocumentFragment) {
elements = root.querySelectorAll('*');
links = root.querySelectorAll('a');
scripts = root.querySelectorAll('script');
buttons = root.querySelectorAll('button');
} else if (root is Document) {
_validateCanonicalLink(root.querySelector('head')!);
elements = root.querySelectorAll('*');
links = root.querySelectorAll('a');
scripts = root.querySelectorAll('script');
buttons = root.querySelectorAll('button');
} else {
throw AssertionError('Unknown html element type: $root');
}
// No inline JS attribute
for (Element elem in elements) {
for (final attr in elem.attributes.keys) {
final name = attr.toString();
if (name.toLowerCase().startsWith('on')) {
throw AssertionError(
'No inline JS attribute is allowed, found: ${elem.outerHtml}.');
}
}
}
// All <a target="_blank"> links must have rel="noopener"
for (Element elem in links) {
if (elem.attributes['target'] == '_blank') {
if (!elem.attributes.containsKey('rel')) {
throw AssertionError(
'_blank links must have rel=noopener, found: ${elem.outerHtml}.');
}
final rel = elem.attributes['rel']!;
if (!rel.split(' ').contains('noopener')) {
throw AssertionError(
'_blank links must have rel=noopener, found: ${elem.outerHtml}.');
}
}
}
// No inline script tag.
for (Element elem in scripts) {
if (elem.attributes['type'] == 'application/ld+json') {
if (elem.attributes.length != 1) {
throw AssertionError(
'Only a single attribute is allowed on ld+json, found: ${elem.outerHtml}');
}
if (elem.text.trim().isEmpty) {
throw AssertionError('ld+json element must not be empty.');
}
// trigger parsing of the content
final map = json.decode(elem.text) as Map;
final context = map['@context'];
if (context is String) {
final isHttpOrHttps =
context.startsWith('http://') || context.startsWith('https://');
if (!isHttpOrHttps) {
throw AssertionError('Invalid @context value: $map');
}
}
} else {
final src = elem.attributes['src'];
if (src == null || src.isEmpty) {
throw AssertionError(
'script tag must have src attribute, found: ${elem.parent?.outerHtml}');
}
if (elem.text.trim().isNotEmpty) {
throw AssertionError(
'script tag must text content must be empty, found: ${elem.outerHtml}');
}
}
}
// Lighthouse flags buttons that don't have text content or an `aria-label` property.
for (final elem in buttons) {
final text = elem.attributes['aria-label']?.trim() ?? elem.text.trim();
if (text.isEmpty) {
// Exempt buttons in dartdoc output:
// TODO: remove after dartdoc content is updated.
if (elem.outerHtml ==
'<button id=\"sidenav-left-toggle\" type=\"button\"> </button>') {
continue;
}
throw AssertionError(
'button tag text content or aria-label must not be empty, found: ${elem.outerHtml}');
}
}
}