diff --git a/pkgs/html/CHANGELOG.md b/pkgs/html/CHANGELOG.md
index 9a881e9d7..eaa1f8c00 100644
--- a/pkgs/html/CHANGELOG.md
+++ b/pkgs/html/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.15.5+1
+
+- Support "ambiguous ampersand" in attribute values.
+
## 0.15.5
- Require Dart `3.2`.
diff --git a/pkgs/html/lib/src/tokenizer.dart b/pkgs/html/lib/src/tokenizer.dart
index 3bed0b0b6..228087c3d 100644
--- a/pkgs/html/lib/src/tokenizer.dart
+++ b/pkgs/html/lib/src/tokenizer.dart
@@ -313,7 +313,6 @@ class HtmlTokenizer implements Iterator {
// Try to find the longest entity the string will match to take care
// of ¬i for instance.
-
int entityLen;
for (entityLen = charStack.length - 1; entityLen > 1; entityLen--) {
final possibleEntityName = charStack.sublist(0, entityLen).join();
@@ -340,7 +339,11 @@ class HtmlTokenizer implements Iterator {
output = '$output${slice(charStack, entityLen).join()}';
}
} else {
- _addToken(ParseErrorToken('expected-named-entity'));
+ if (!fromAttribute) {
+ // Only emit this error token when we're consuming this NOT as part of an attribute.
+ // See: https://html.spec.whatwg.org/multipage/parsing.html#ambiguous-ampersand-state
+ _addToken(ParseErrorToken('expected-named-entity'));
+ }
stream.unget(charStack.removeLast());
output = '&${charStack.join()}';
}
diff --git a/pkgs/html/pubspec.yaml b/pkgs/html/pubspec.yaml
index 447b98e18..7508588ad 100644
--- a/pkgs/html/pubspec.yaml
+++ b/pkgs/html/pubspec.yaml
@@ -1,5 +1,5 @@
name: html
-version: 0.15.5
+version: 0.15.5+1
description: APIs for parsing and manipulating HTML content outside the browser.
repository: https://github.com/dart-lang/tools/tree/main/pkgs/html
issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Ahtml
diff --git a/pkgs/html/test/data/tokenizer/test4.test b/pkgs/html/test/data/tokenizer/test4.test
index c0f3b2b8c..6c0a77ce1 100644
--- a/pkgs/html/test/data/tokenizer/test4.test
+++ b/pkgs/html/test/data/tokenizer/test4.test
@@ -28,6 +28,10 @@
"input":"",
"output":["ParseError", "ParseError", "ParseError", ["StartTag", "z", {"=": "=="}]]},
+{"description":"Ambiguous ampersand in attribute value",
+"input":"",
+"output":[["StartTag", "tag", {"attr": "foo?a=b&c=d"}]]},
+
{"description":"Allowed \" after ampersand in attribute value",
"input":"",
"output":[["StartTag", "z", {"z": "&"}]]},
diff --git a/pkgs/html/test/parser_feature_test.dart b/pkgs/html/test/parser_feature_test.dart
index 7156146e0..df9172ee7 100644
--- a/pkgs/html/test/parser_feature_test.dart
+++ b/pkgs/html/test/parser_feature_test.dart
@@ -148,6 +148,18 @@ On line 4, column 3 of ParseError: Unexpected DOCTYPE. Ignored.
expect(elem.attributeSpans!['extends'], null);
});
+ test('attribute spans if value contains & (ambiguous ampersand)', () {
+ final expectedUrl = 'foo?key=value&key2=value2';
+ final text = '';
+
+ final doc = parse(text, generateSpans: true);
+ final elem = doc.querySelector('script')!;
+ final span = elem.attributeValueSpans!['src']!;
+
+ expect(span.start.offset, text.indexOf('foo'));
+ expect(span.text, expectedUrl);
+ });
+
test('void element innerHTML', () {
var doc = parse('');
expect(doc.body!.innerHtml, '');